Работа с датами и временем в Kotlin: продвинутые техники и API

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

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

Начать курс

Продвинутая работа с датами и временем в Kotlin

В современной разработке на Kotlin работа с датами и временем выходит далеко за рамки простого вывода текущей даты. На продвинутом уровне требуется учитывать часовые пояса, выполнять сложные вычисления с периодами, парсить нестандартные форматы и обрабатывать временные метки в распределённых системах. В этой статье мы рассмотрим ключевые библиотеки и техники, которые помогут вам писать надёжный и эффективный код для работы с датами и временем на Kotlin.

1. Выбор библиотеки: java.time vs kotlinx-datetime

Kotlin полностью совместим с Java, поэтому вы можете использовать мощный API java.time (JSR-310), который является стандартом для новых проектов. Однако для мультиплатформенных проектов (KMP) лучше выбрать kotlinx-datetime от JetBrains.

java.time (JVM)

Основные классы: LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant, Duration, Period.

import java.time.*

fun main() {
    val today = LocalDate.now()
    val meetingTime = LocalDateTime.of(2025, 3, 15, 14, 30)
    val zonedMeeting = ZonedDateTime.of(meetingTime, ZoneId.of("Europe/Moscow"))
    println("Встреча в Москве: $zonedMeeting")
}

kotlinx-datetime (Multiplatform)

Добавьте зависимость в build.gradle.kts:

implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
import kotlinx.datetime.*

fun main() {
    val now = Clock.System.now()
    val date = now.toLocalDateTime(TimeZone.of("Europe/Moscow")).date
    println("Сегодня: $date")
}

2. Парсинг и форматирование дат

Корректный парсинг — основа работы с внешними данными. Рассмотрим продвинутые приёмы.

Парсинг с нестандартными форматами

import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException

fun parseCustomDate(dateString: String): LocalDateTime? {
    val formatters = listOf(
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
        DateTimeFormatter.ofPattern("dd/MM/yyyy'T'HH:mm"),
        DateTimeFormatter.ISO_LOCAL_DATE_TIME
    )
    for (formatter in formatters) {
        try {
            return LocalDateTime.parse(dateString, formatter)
        } catch (e: DateTimeParseException) {
            // пробуем следующий
        }
    }
    return null
}

fun main() {
    println(parseCustomDate("2025-03-15 14:30:00")) // 2025-03-15T14:30
    println(parseCustomDate("15/03/2025T14:30"))    // 2025-03-15T14:30
}

Локализованное форматирование

import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Locale

fun main() {
    val date = LocalDate.of(2025, 3, 15)
    val usFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL)
        .withLocale(Locale.US)
    val ruFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL)
        .withLocale(Locale("ru"))
    println("USA: ${date.format(usFormatter)}")  // Saturday, March 15, 2025
    println("RU:  ${date.format(ruFormatter)}")  // суббота, 15 марта 2025 г.
}

3. Сложные вычисления с Duration и Period

Duration — для точных интервалов времени (часы, минуты, секунды), Period — для календарных периодов (годы, месяцы, дни).

Разница между датами с учётом бизнес-логики

import java.time.*
import java.time.temporal.ChronoUnit

fun businessDaysBetween(start: LocalDate, end: LocalDate): Long {
    var count = 0L
    var current = start
    while (current.isBefore(end)) {
        if (current.dayOfWeek != DayOfWeek.SATURDAY && 
            current.dayOfWeek != DayOfWeek.SUNDAY) {
            count++
        }
        current = current.plusDays(1)
    }
    return count
}

fun main() {
    val start = LocalDate.of(2025, 3, 10) // понедельник
    val end = LocalDate.of(2025, 3, 17)   // понедельник
    println("Рабочих дней: ${businessDaysBetween(start, end)}") // 5
}

Работа с Instant и временными метками

import java.time.*

fun main() {
    val start = Instant.now()
    Thread.sleep(1500) // имитация работы
    val end = Instant.now()
    val duration = Duration.between(start, end)
    println("Прошло: ${duration.seconds} секунд")
    println("В миллисекундах: ${duration.toMillis()}")

    // Сложение Duration с Instant
    val future = start.plus(Duration.ofHours(2))
    println("Через 2 часа: $future")
}

4. Таймзоны и переход на летнее время

Одна из самых сложных тем — корректная обработка часовых поясов, особенно при переводе часов.

Безопасное преобразование между таймзонами

import java.time.*

fun convertTimeZone(
    dateTime: LocalDateTime, 
    fromZone: ZoneId, 
    toZone: ZoneId
): ZonedDateTime {
    val zonedFrom = ZonedDateTime.of(dateTime, fromZone)
    return zonedFrom.withZoneSameInstant(toZone)
}

fun main() {
    val meeting = LocalDateTime.of(2025, 3, 30, 2, 30) // ночь перехода на летнее время
    val moscow = ZoneId.of("Europe/Moscow")
    val london = ZoneId.of("Europe/London")
    
    val result = convertTimeZone(meeting, moscow, london)
    println("В Москве: $meeting MSK -> В Лондоне: $result")
    // Обратите внимание: 30 марта 2025 в 2:00 MSK -> 3:00 MSK (переход)
    // Kotlin корректно обработает этот случай
}

Получение смещения для конкретной даты

import java.time.*

fun printOffsetInfo(zoneId: String) {
    val zone = ZoneId.of(zoneId)
    val now = LocalDateTime.now()
    val zoned = ZonedDateTime.of(now, zone)
    val offset = zoned.offset
    println("Текущее смещение для $zoneId: $offset")
    
    // Проверка, действует ли летнее время
    val rules = zone.r                

Блоги

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