MpLocalDateTime

A date and time without timezone. Deliberately the most restricted type — it exists to represent "what the user typed" before a timezone is known.

When to use MpLocalDateTime

  • Form input: a user picks "March 15 at 2:30 PM" in a date-time picker
  • Display purposes where the timezone is shown separately
  • Intermediate value before the timezone is determined
  • Calendar events in a "floating" timezone (the same local time everywhere)

Creating

import io.peekandpoke.ultra.datetime.MpLocalDateTime

val dt = MpLocalDateTime.of(2025, 3, 15, 14, 30, 0)
val withMonth = MpLocalDateTime.of(2025, Month.MARCH, 15, 14, 30)
val fromParts = MpLocalDateTime.of(
    date = MpLocalDate.of(2025, 3, 15),
    time = MpLocalTime.of(14, 30),
)
val parsed = MpLocalDateTime.parse("2025-03-15T14:30:00")
val maybe = MpLocalDateTime.tryParse("not-valid")  // null

// Boundaries
val genesis = MpLocalDateTime.Genesis
val doomsday = MpLocalDateTime.Doomsday

Properties

val dt = MpLocalDateTime.of(2025, 3, 15, 14, 30, 45)

dt.year          // 2025
dt.monthNumber   // 3
dt.month         // Month.MARCH
dt.dayOfMonth    // 15
dt.hour          // 14
dt.minute        // 30
dt.second        // 45
dt.milliSecond   // 0
dt.dayOfWeek     // DayOfWeek.SATURDAY
dt.dayOfYear     // 74

What MpLocalDateTime deliberately does not have

This is the key design decision: MpLocalDateTime has no arithmetic operations. No plusDays(), no plusHours(), no plusMonths().

This is not an oversight. Adding time to a local datetime is ambiguous near DST transitions. "2:30 AM + 1 hour" on the night clocks spring forward is undefined without knowing which timezone. Rather than giving wrong answers silently, the library makes you choose the right type first.

If you want calendar arithmetic, use MpLocalDate (no DST issue for pure dates) or MpZonedDateTime (carries its timezone, handles DST automatically). If you want absolute-time arithmetic, convert to MpInstant first.

Also missing, for the same reason:

  • No anchoring operations (atStartOfMonth(), etc.) — use MpLocalDate or MpZonedDateTime
  • No duration addition — convert to MpInstant or MpZonedDateTime first

Decomposing

val dt = MpLocalDateTime.of(2025, 3, 15, 14, 30)

val date = dt.toDate()  // MpLocalDate(2025-03-15)
val time = dt.toTime()  // MpLocalTime(14:30:00)

Converting to absolute types (requires timezone)

The moment you need to pin this datetime to a real moment in time, you must provide a timezone:

val dt = MpLocalDateTime.of(2025, 3, 15, 14, 30)

// To MpInstant — REQUIRES timezone
val instant = dt.toInstant(MpTimezone.of("Europe/Berlin"))
val instantUtc = dt.toInstant(MpTimezone.UTC)

// To MpZonedDateTime — REQUIRES timezone
val zoned = dt.atZone(MpTimezone.of("Europe/Berlin"))
val utc = dt.atUTC()  // shorthand for atZone(UTC)
These are the only escape routes from "local" to "absolute," and both require a timezone. This is the guard rail that prevents the most common class of timezone bugs.

Formatting

val dt = MpLocalDateTime.of(2025, 3, 15, 14, 30)

dt.toIsoString()  // "2025-03-15T14:30:00"

JVM interop

// MpLocalDateTime -> java.time.LocalDateTime
val javaDateTime: java.time.LocalDateTime = mpDateTime.jvm