---
title: "kotlinx-datetime em Kotlin: datas, Instant e fuso horário em 2026"
url: "https://kotlin.dev.br/blog/kotlinx-datetime-kotlin-datas-fuso-horario-2026/"
markdown_url: "https://kotlin.dev.br/blog/kotlinx-datetime-kotlin-datas-fuso-horario-2026.MD"
description: "Guia prático de kotlinx-datetime: Instant, LocalDateTime, fuso horário, KMP, Room/SQLDelight e erros comuns de timezone em apps Android e backend Kotlin."
date: "2026-08-17"
author: "Karina Melo"
---

# kotlinx-datetime em Kotlin: datas, Instant e fuso horário em 2026

Guia prático de kotlinx-datetime: Instant, LocalDateTime, fuso horário, KMP, Room/SQLDelight e erros comuns de timezone em apps Android e backend Kotlin.


**Resposta rápida:** use `kotlinx-datetime` para modelar tempo de forma tipada em Kotlin (Android, backend e KMP). Guarde eventos absolutos como `Instant` (UTC), derive calendário com `TimeZone` e `toLocalDateTime()`, e use `LocalDate` / `LocalDateTime` / `LocalTime` só quando o valor for “civil” (aniversário, horário de funcionamento, data de vencimento sem instante único). Prefira `Clock.System.now()` injetável nos testes em vez de `Clock.System` espalhado. Não confunda `java.util.Date`, `Calendar` e strings `dd/MM/yyyy` com um modelo de domínio.

Datas quebram produtos silenciosamente: um agendamento “às 09:00” vira 12:00 para quem está em outro fuso, um relatório diário corta o dia errado no horário de verão, e um teste flaky passa só no laptop do autor. O ecossistema Kotlin já aponta `kotlinx-datetime` em tutoriais de [Kotlin Multiplatform](/tutoriais/kotlin-multiplatform-tutorial/), glossário de [expect/actual](/glossario/expect-actual/) e guias KMP — mas faltava um guia prático dono do tópico. Este artigo fecha esse buraco com decisões, código e checklist de produção.

## Por que kotlinx-datetime e não java.time puro?

No JVM você pode usar `java.time` (`Instant`, `ZonedDateTime`, `LocalDate`). Em KMP, iOS e common code, `java.time` não está disponível da mesma forma. `kotlinx-datetime` oferece um modelo compartilhado:

| Tipo | Significado | Quando usar |
|---|---|---|
| `Instant` | ponto no tempo (UTC) | eventos, logs, `createdAt`, expiração de token |
| `LocalDate` | ano-mês-dia civil | aniversário, data de fatura, feriado |
| `LocalTime` | hora civil sem data | horário de abertura da loja |
| `LocalDateTime` | data + hora civil sem fuso | “reunião em 17/08 às 14:00” *nessa* região |
| `TimeZone` | regras de fuso / offset | converter Instant ↔ calendário |
| `DateTimePeriod` / `DatePeriod` | duração calendárica | “daqui a 1 mês”, “+2 dias úteis” (com cuidado) |
| `Clock` | fonte de “agora” | produção e testes |

A biblioteca não substitui serialização JSON sozinha: combine com [kotlinx.serialization](/blog/kotlin-serialization-avancada-polimorfismo-2026/) e contratos explícitos de ISO-8601. Em backends Spring/Ktor no JVM, muitos times ainda usam `java.time` na borda HTTP e convertem para o domínio — o importante é **uma** representação canônica por conceito.

## Dependência e imports

No Gradle (Version Catalog recomendado):

```kotlin
// libs.versions.toml
[versions]
kotlinx-datetime = "0.6.2" // confira a estável atual no repositório oficial

[libraries]
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
```

```kotlin
dependencies {
    implementation(libs.kotlinx.datetime)
}
```

Em commonMain de um projeto KMP:

```kotlin
kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation(libs.kotlinx.datetime)
        }
    }
}
```

Imports típicos:

```kotlin
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
```

Não fixe versões “mágicas” em snippets de blog: alinhe ao catálogo do monorepo e atualize por PR, como no [guia de Gradle com Kotlin](/guias/guia-kotlin-gradle/).

## Instant: o tipo padrão para eventos

Quase todo evento de sistema deve nascer como `Instant`:

```kotlin
import kotlin.time.Duration.Companion.minutes

val agora: Instant = Clock.System.now()
val expiraEm: Instant = agora + 15.minutes
```

Para intervalos absolutos, prefira `kotlin.time.Duration` com `Instant` (operadores da biblioteca) em vez de somar dias civis “no escuro”. Quando o negócio fala em “daqui a 30 dias de calendário”, use `DatePeriod` / `DateTimePeriod` **depois** de escolher o fuso.

Exemplo de domínio limpo:

```kotlin
data class Assinatura(
    val id: String,
    val criadaEm: Instant,
    val renovavelAte: Instant,
)

fun Assinatura.estaAtiva(agora: Instant = Clock.System.now()): Boolean =
    agora < renovavelAte
```

Injete `Clock` (ou `() -> Instant`) em ViewModels, services e use cases. Isso evita testes que dependem do relógio da máquina e facilita simular “amanhã” em cenários de [WorkManager](/blog/workmanager-kotlin-android-2026/) e offline-first.

## LocalDate e calendário civil

Use `LocalDate` quando o fuso não faz parte do significado:

```kotlin
val aniversario = LocalDate(1990, 5, 17)
val hojeEmSp = Clock.System.now()
    .toLocalDateTime(TimeZone.of("America/Sao_Paulo"))
    .date

fun fazAniversarioHoje(aniversario: LocalDate, hoje: LocalDate): Boolean =
    aniversario.month == hoje.month && aniversario.dayOfMonth == hoje.dayOfMonth
```

Erros clássicos:

1. Guardar aniversário como `Instant` à meia-noite UTC — o dia “vira” conforme o fuso do cliente.
2. Comparar strings `17/05/1990` sem parse tipado.
3. Usar `LocalDateTime` quando só a data importa.

Para faturas e fechamento de cartão no Brasil, combine `LocalDate` com a regra de negócio (próximo dia útil, etc.) em código explícito — não esconda isso em formatação.

## Fuso horário: America/Sao_Paulo e amigos

Conversão correta:

```kotlin
val zonaSp = TimeZone.of("America/Sao_Paulo")
val instante = Instant.parse("2026-08-17T15:00:00Z")
val civilEmSp: LocalDateTime = instante.toLocalDateTime(zonaSp)
val deVolta: Instant = civilEmSp.toInstant(zonaSp)
```

Regras práticas para times brasileiros:

- **Armazene Instant (UTC) no banco e na API.** Exiba no fuso do usuário ou no fuso do negócio (`America/Sao_Paulo`) na UI.
- **Não use `TimeZone.currentSystemDefault()` no servidor** para regras de negócio: o pod pode estar em UTC. Fixe o fuso do produto.
- **IDs IANA** (`America/Sao_Paulo`, `America/Manaus`) são mais seguros que offsets fixos (`-03:00`) quando há histórico de horário de verão ou mudanças legais.
- Em apps mobile, o fuso do aparelho importa para UX; o contrato da API deve documentar se o campo é Instant ISO-8601 (`…Z`) ou data civil.

Se você monta APIs Ktor, alinhe serialização e contratos com o que já faz em [cliente resiliente](/blog/ktor-client-resiliente-timeout-retry-circuit-breaker-2026/) e testes com [testApplication](/blog/testes-ktor-testapplication-kotlin-2026/): um campo `createdAt` ambíguo é bug de produto, não “detalhe de front”.

## Parsing, formatação e ISO-8601

Prefira ISO-8601 na borda:

```kotlin
val instant = Instant.parse("2026-08-17T18:30:00Z")
val data = LocalDate.parse("2026-08-17")
```

Para exibir em PT-BR na UI Android, muitos times formatam a partir de `java.time` no `androidMain` ou usam formatadores da plataforma após converter. Em common code, mantenha o valor tipado e deixe a formatação na camada de apresentação. Evite guardar `String` formatada no banco.

Checklist de contrato HTTP:

1. Campos absolutos: `2026-08-17T18:30:00Z` (Instant).
2. Campos civis: `2026-08-17` (LocalDate) — documente o significado.
3. Nunca misture `18/08/2026 15:30` sem fuso em JSON de integração.
4. Rejeite payloads ambíguos cedo (400) com mensagem clara.

## Persistência: Room, SQLDelight e SQL

Em Android com Room, tipos customizados convertem `Instant` ↔ `Long` (epoch millis) ou `String` ISO. Em KMP com [SQLDelight](/blog/sqldelight-kotlin-multiplatform-android-ios-2026/) ou [Room KMP](/blog/room-kotlin-multiplatform-2026/), defina adapters explícitos:

```kotlin
// ideia: Instant ↔ Long (epoch milliseconds)
fun Instant.asEpochMillis(): Long = toEpochMilliseconds()
fun Long.toKotlinInstant(): Instant = Instant.fromEpochMilliseconds(this)
```

Cuidados:

- **Migrations** de coluna `TEXT` “dd/MM/yyyy” para Instant exigem script e validação — veja o espírito do guia de [migrations Room](/blog/migrations-room-android-kotlin-2026/).
- Índices em epoch facilitam ranges (`WHERE created_at BETWEEN ? AND ?`).
- Relatórios “por dia” no fuso de São Paulo devem converter Instant → `LocalDate` **na query ou na aplicação** com o fuso correto; agrupar só por UTC meia-noite distorce o dia comercial brasileiro.

Arquiteturas [offline-first](/blog/android-offline-first-kotlin-2026/) sincronizam melhor quando o relógio canônico é Instant e o conflito é resolvido com `updatedAt` absoluto.

## Testes determinísticos com Clock

```kotlin
import kotlin.time.Duration.Companion.days

class FakeClock(private var agora: Instant) : Clock {
    override fun now(): Instant = agora
    fun avancar(duration: kotlin.time.Duration) {
        agora = agora + duration
    }
}

@Test
fun renovacaoExpira() {
    val clock = FakeClock(Instant.parse("2026-08-17T12:00:00Z"))
    val assinatura = Assinatura(
        id = "1",
        criadaEm = clock.now(),
        renovavelAte = clock.now() + 1.days,
    )
    clock.avancar(2.days)
    assertFalse(assinatura.estaAtiva(clock.now()))
}
```

Combine com a pirâmide de testes do site ([Turbine/Flow](/blog/testando-flow-stateflow-turbine-kotlin-2026/), [Testcontainers](/blog/testcontainers-kotlin-postgresql-testes-integracao-2026/), [Kotest](/blog/kotest-kotlin-property-based-testing-2026/)): propriedades de calendário (“todo Instant tem LocalDate em SP”) são ótimas candidatas a property-based testing.

## KMP: common code sem expect/actual para datas

Para a maioria dos casos, `kotlinx-datetime` elimina `expect/actual` de parsing e aritmética. Reserve `expect/actual` para integração com APIs nativas de calendário/UI. O glossário de [expect/actual](/glossario/expect-actual/) já recomenda avaliar bibliotecas multiplataforma antes de duplicar implementação — datas são o exemplo canônico.

Quem busca carreira KMP encontra o encaixe em [desenvolvedor Kotlin Multiplatform](/carreira/desenvolvedor-kotlin-multiplatform/) e no [guia KMP mobile](/guias/guia-kotlin-multiplatform-mobile/): domínio compartilhado com Instant/LocalDate é um dos ganhos mais tangíveis em entrevistas.

## Erros comuns (e como evitar)

1. **Somar 24 horas para “amanhã”** em vez de `DatePeriod(days = 1)` no fuso certo — quebra em mudanças de offset.
2. **Persistir `LocalDateTime` sem fuso** para eventos online — dois usuários veem horários incoerentes.
3. **Usar `Date()` / `Calendar` legado** em código novo Android — migre na borda e isole.
4. **Confiar no fuso do servidor** em Kubernetes — configure `America/Sao_Paulo` só onde for regra de negócio; logs em UTC.
5. **Comparar strings formatadas** em testes de UI — compare `LocalDate` / `Instant`.
6. **Esquecer monotonicidade vs wall clock** — para timeouts use `TimeSource` / medições; para negócio use `Clock` de wall time documentado.
7. **Serializar Instant como epoch em um serviço e ISO em outro** sem gateway — padronize o contrato.

## Quando NÃO usar kotlinx-datetime

- Código JVM-only legado já saturado de `java.time` com adapters estáveis: introduza kotlinx-datetime só se houver plano KMP ou modelo de domínio compartilhado.
- Cálculos astronômicos ou calendários litúrgicos especializados: use bibliotecas de domínio.
- Cron expressions de infraestrutura (K8s, GitHub Actions): mantenha no formato da ferramenta.

## Checklist de adoção em 2026

1. Inventariar campos de data no domínio: classificar Instant vs LocalDate vs LocalDateTime.
2. Adicionar dependência no catálogo; proibir `java.util.Date` em código novo (Detekt/ktlint se possível).
3. Definir fuso canônico do produto (`America/Sao_Paulo` para regras BR) e UTC no armazenamento.
4. Injetar `Clock` nas camadas de aplicação.
5. Documentar o contrato JSON (ISO-8601).
6. Cobrir conversões críticas com testes de fuso (SP, Manaus, UTC).
7. Revisar jobs agendados ([WorkManager](/blog/workmanager-kotlin-android-2026/), cron Ktor/Spring) para não misturar civil e absoluto.

## Perguntas frequentes

### kotlinx-datetime substitui java.time no Android?

No `androidMain`/`jvmMain` você pode continuar usando `java.time` na UI ou em bibliotecas JVM. Em `commonMain` e em estratégias KMP, `kotlinx-datetime` é a escolha natural. Muitos apps convertem na fronteira do módulo shared.

### Como representar “todo dia às 09:00 em São Paulo”?

Modele a regra civil (`LocalTime(9, 0)` + `TimeZone.of("America/Sao_Paulo")`) e materialize o próximo `Instant` quando for agendar a execução. Não grave só um Instant único se a regra for recorrente.

### Posso usar kotlinx-datetime com Exposed ou Spring Data?

Sim, com converters. No ecossistema Exposed ([Exposed 1.0 / R2DBC](/blog/exposed-1-0-r2dbc-kotlin-2026/)) mapeie colunas timestamp/timestamptz para Instant. Em Spring, `java.time` ainda é o default — converta na borda do domínio.

### Qual a diferença entre Duration e DatePeriod?

`Duration` (kotlin.time) é tempo físico (segundos/nanos). `DatePeriod` / `DateTimePeriod` é calendário (dias/meses/anos) e depende do contexto civil. “Mais 30 dias” de fatura ≠ “mais 30 × 24 horas”.

### kotlinx-datetime serve para iOS via KMP?

Sim. É uma das bibliotecas citadas em [Kotlin para iOS](/perguntas/kotlin-para-ios/) junto com Ktor, SQLDelight e serialization. Valide formatação e calendário nativo na camada iOS quando a UX exigir `NSCalendar`.

## Conclusão e próximos passos

`kotlinx-datetime` deixa explícito o que muitos apps escondem em `String` e `Long` mágicos: se o valor é um instante absoluto ou uma data civil, e em qual fuso a regra de negócio vive. Para times brasileiros, a combinação **Instant no armazenamento + `America/Sao_Paulo` na regra civil + `Clock` injetável** elimina a maior parte dos bugs de “funcionou no meu emulador”.

Continue no cluster: [tutorial KMP](/tutoriais/kotlin-multiplatform-tutorial/), [estrutura de projetos KMP](/blog/kmp-nova-estrutura-projetos-2026/), [coroutines](/tutoriais/coroutines-tutorial-basico/), [serialization](/blog/kotlin-serialization-avancada-polimorfismo-2026/) e [vagas Kotlin](/vagas/) se o próximo passo for aplicar isso em produção. Se a dúvida for carreira multiplataforma, o guia de [desenvolvedor KMP](/carreira/desenvolvedor-kotlin-multiplatform/) fecha o caminho do shared domain até o portfólio.
