Продвинутая работа с датами и временем в Kotlin
В современной разработке на Kotlin грамотная обработка дат и времени — не просто удобство, а необходимость. На продвинутом уровне разработчику приходится иметь дело с часовыми поясами, периодами, длительностями и сложными операциями форматирования. В этой статье мы рассмотрим ключевые инструменты и приёмы, которые помогут вам писать надёжный и эффективный код.
1. Основные классы: LocalDate, LocalTime, LocalDateTime и Instant
Kotlin полностью наследует мощную библиотеку java.time (JSR-310), которая стала стандартом для работы с датами и временем в Java 8 и выше. Для Kotlin Multiplatform существует библиотека kotlinx-datetime, но в рамках JVM-проектов наиболее популярным остаётся java.time.
LocalDate, LocalTime, LocalDateTime
Эти классы представляют дату и время без привязки к часовому поясу. Они идеальны для хранения дней рождения, расписаний и других данных, где часовой пояс не важен.
import java.time.LocalDate
import java.time.LocalTime
import java.time.LocalDateTime
fun main() {
val date = LocalDate.now()
val time = LocalTime.of(14, 30, 0)
val dateTime = LocalDateTime.of(date, time)
println("Дата: $date")
println("Время: $time")
println("Дата и время: $dateTime")
// Сложение и вычитание дней
val tomorrow = date.plusDays(1)
println("Завтра: $tomorrow")
}
Instant — метка времени для машин
Instant представляет собой точку на временной шкале в UTC. Он незаменим для логирования, обмена данными между серверами и работы с API.
import java.time.Instant
fun main() {
val now = Instant.now()
println("Текущий момент в UTC: $now")
// Конвертация из миллисекунд
val fromEpoch = Instant.ofEpochMilli(1_700_000_000_000)
println("Из миллисекунд: $fromEpoch")
}
2. Часовые пояса: ZonedDateTime и OffsetDateTime
Когда приложение работает с пользователями из разных регионов, необходимо учитывать часовые пояса. Для этого существуют ZonedDateTime (с полной информацией о зоне) и OffsetDateTime (только смещение от UTC).
import java.time.ZonedDateTime
import java.time.ZoneId
import java.time.ZoneOffset
fun main() {
// Текущее время в Нью-Йорке
val newYork = ZonedDateTime.now(ZoneId.of("America/New_York"))
println("Нью-Йорк: $newYork")
// Преобразование из UTC
val utcNow = ZonedDateTime.now(ZoneOffset.UTC)
val moscowTime = utcNow.withZoneSameInstant(ZoneId.of("Europe/Moscow"))
println("Москва: $moscowTime")
// OffsetDateTime для простых смещений
val offsetTime = java.time.OffsetDateTime.now(ZoneOffset.ofHours(3))
println("+3 часа: $offsetTime")
}
Важно: Для пользовательского интерфейса всегда используйте ZonedDateTime, чтобы корректно обрабатывать переход на летнее/зимнее время. OffsetDateTime подходит для фиксированных смещений, например, при работе с логами.
3. Разница между датами: Period и Duration
Для вычисления разницы во времени Kotlin предоставляет два класса: Period (для дат, в днях/месяцах/годах) и Duration (для точных интервалов в секундах/наносекундах).
Period — работа с календарными единицами
import java.time.LocalDate
import java.time.Period
fun main() {
val birthDate = LocalDate.of(1990, 5, 15)
val today = LocalDate.now()
val age = Period.between(birthDate, today)
println("Возраст: ${age.years} лет, ${age.months} месяцев, ${age.days} дней")
// Добавление периода к дате
val nextMonth = today.plus(Period.ofMonths(1))
println("Через месяц: $nextMonth")
}
Duration — точные интервалы
import java.time.Duration
import java.time.Instant
fun main() {
val start = Instant.now()
Thread.sleep(1500) // симуляция работы
val end = Instant.now()
val duration = Duration.between(start, end)
println("Прошло: ${duration.seconds} секунд и ${duration.nano} наносекунд")
// Сравнение длительностей
val timeout = Duration.ofSeconds(2)
if (duration.compareTo(timeout) < 0) {
println("Операция выполнена быстрее таймаута")
}
}
4. Форматирование и парсинг с помощью DateTimeFormatter
Библиотека java.time.format.DateTimeFormatter позволяет гибко настраивать вывод дат и читать строки из внешних источников. На продвинутом уровне часто требуется работа с локализованными форматами.
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Locale
fun main() {
val now = LocalDateTime.now()
// Пользовательский формат
val customFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss")
println("Пользовательский: ${now.format(customFormatter)}")
// Локализованный формат (для США)
val usFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
.withLocale(Locale.US)
println("США: ${now.format(usFormatter)}")
// Парсинг строки
val dateString = "2024-12-25T10:30:00"
val parsedDate = LocalDateTime.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
println("Распарсено: $parsedDate")
// Локализованный парсинг (русский)
val russianFormatter = DateTimeFormatter.ofPattern("d MMMM yyyy", Locale("ru"))
val russianDate = LocalDate.parse("15 мая 2024", russianFormatter)
println("Русская дата: $russianDate")
}
Совет: Если ваше приложение поддерживает несколько языков, используйте ofLocalizedDateTime() с разными Locale. Это избавит вас от ручного управления десятками шаблонов.
5. Продвинутые техники: работа с временными интервалами и таймзонами
На практике часто требуется не просто получить разницу, а проверить, пересекаются ли два временных интервала,