A Ballerina wrapper library around java.time — providing LocalDate, LocalDateTime, and LocalTime for working with dates and times without time zones.
Note on scope: This library wraps only the members that are fully implemented. A few Java-side wrapper types (
Month,DayOfWeek,IsoEra,Chronology,IsoChronology,Class) are declared as Java bindings internally but have no public members yet, so any method that would return or accept one of those types (e.g.getMonth(),getDayOfWeek(),getEra(),getChronology(),getClass()) is intentionally left out of this documentation and out of the public API for now.
- Ballerina Swan Lake
2201.13.4or later - Java 21 runtime (bundled with the Ballerina distribution — no separate install needed)
# Ballerina.toml
[[dependency]]
org = "kruutteri1"
name = "java_time_utils"
version = "1.0.3" # check central.ballerina.io for the latest versionimport kruutteri1/java_time_utils.javatime as jt;- Quick Start
- LocalDate
- LocalDateTime
- LocalTime
- A Note on Method Naming
- Error Handling
- Inherited Java Object Methods
import ballerina/io;
import kruutteri1/java_time_utils.javatime as jt;
public function main() {
jt:LocalDate date = jt:ofDate(2026, 7, 15);
jt:LocalTime time = jt:ofTimeWithSecond(14, 28, 19);
jt:LocalDateTime dateTime = jt:ofLocalDateWithLocalTime(date, time);
io:println(date.toString());
io:println(time.toString());
io:println(dateTime.toString());
}A date without a time-of-day or time zone component, such as 2026-07-15.
getCurrentDate()returnsLocalDate— current date from the system clockofDate(int year, int month, int day)returnsLocalDate— panics on invalid inputofEpochDay(int epochDay)returnsLocalDateofYearDay(int year, int dayOfYear)returnsLocalDate— panics if dayOfYear exceeds the year lengthgetMINDate()returnsLocalDate— minimum supported dategetMAXDate()returnsLocalDate— maximum supported dategetEPOCHDate()returnsLocalDate— 1970-01-01
jt:LocalDate d = jt:ofDate(2026, 7, 15);Output: toString() returns string
Component getters (no args, return int): getYear(), getMonthValue(), getDayOfMonth(), getDayOfYear()
Comparison: isEquals(Object other) returns boolean — returns false rather than panicking on a type mismatch. hashCode() also available (see Inherited Java Object Methods).
Checks: isLeapYear(), lengthOfMonth(), lengthOfYear(), toEpochDay()
Combining with time (produces LocalDateTime):
atStartOfDay()— 00:00atTime(int hour, int minute)atTimeDetailed(int hour, int minute, int second)atTimeFull(int hour, int minute, int second, int nano)atLocalTime(LocalTime time)— combines with an already-built LocalTime; never panics
Date ranges:
datesUntil(LocalDate endExclusive)returnsStream— every date up to (not including) endExclusive. TheStreamtype is a low-level binding not documented in depth here.
Arithmetic (immutable, returns new LocalDate):
- Add:
plusYears,plusMonths,plusWeeks,plusDays - Subtract:
minusYears,minusMonths,minusWeeks,minusDays plusMonths/plusYears/minusMonths/minusYearsclamp the day-of-month on overflow (e.g. Jan 31 + 1 month → Feb 28/29) — this does not panic.
Altering fields (with*, panics on invalid value instead of clamping):
withYear(int year), withMonth(int month), withDayOfMonth(int dayOfMonth), withDayOfYear(int dayOfYear)
import ballerina/io;
import kruutteri1/java_time_utils.javatime as jt;
public function main() {
jt:LocalDate d = jt:ofDate(2026, 7, 15);
jt:LocalDate nextWeek = d.plusWeeks(1);
jt:LocalDate newYearDay = d.withMonth(1).withDayOfMonth(1);
io:println("Year: ", d.getYear());
io:println("Leap year: ", d.isLeapYear());
io:println(d.toString());
io:println(nextWeek.toString());
jt:LocalDateTime dt = d.atTime(9, 0);
io:println(dt.toString());
jt:LocalDate|error invalid = trap jt:ofDate(2026, 2, 30);
if invalid is error {
io:println("Rejected as expected: ", invalid.message());
}
}A date and time without a time zone, such as 2026-07-15T14:28:19.
getCurrentDateTime()returnsLocalDateTimeofDateTime(int year, int month, int dayOfMonth, int hour, int minute)returnsLocalDateTimeofWithSeconds(int year, int month, int dayOfMonth, int hour, int minute, int second)returnsLocalDateTimeofWithNanos(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond)returnsLocalDateTimeofLocalDateWithLocalTime(LocalDate date, LocalTime time)returnsLocalDateTime— never panics, both inputs already validatedgetMINDateTime()returnsLocalDateTimegetMAXDateTime()returnsLocalDateTime
jt:LocalDateTime dt = jt:ofDateTime(2026, 7, 15, 14, 28);Output: toString() returns string
Component getters: getYear(), getMonthValue(), getDayOfMonth(), getDayOfYear(), getHour(), getMinute(), getSecond(), getNano()
Comparison: isEquals(Object other) returns boolean. hashCode() also available.
Splitting: toLocalDate() returns LocalDate, toLocalTime() returns LocalTime
Arithmetic (immutable):
- Add:
plusYears,plusMonths,plusWeeks,plusDays,plusHours,plusMinutes,plusSeconds,plusNanos - Subtract:
minusYears,minusMonths,minusWeeks,minusDays,minusHours,minusMinutes,minusSeconds,minusNanos ⚠️ Rollover past midnight inplusHours/plusMinutes/etc. advances the calendar date, unlikeLocalTimewhich just wraps within the same day.
Altering fields (with*, panics on invalid value):
withYear, withMonth, withDayOfMonth, withDayOfYear, withHour, withMinute, withSecond, withNano
import ballerina/io;
import kruutteri1/java_time_utils.javatime as jt;
public function main() {
jt:LocalDateTime dt = jt:ofDateTime(2026, 7, 15, 14, 28);
jt:LocalDateTime tomorrow = dt.plusDays(1);
jt:LocalDate d = dt.toLocalDate();
jt:LocalTime t = dt.toLocalTime();
jt:LocalDateTime combined = jt:ofLocalDateWithLocalTime(d, t);
io:println(dt.toString());
io:println(tomorrow.toString());
// Midnight rollover advances the date
jt:LocalDateTime nearMidnight = jt:ofWithSeconds(2026, 7, 15, 23, 30, 0);
jt:LocalDateTime rolledOver = nearMidnight.plusHours(1);
io:println(rolledOver.toString()); // 2026-07-16T00:30
}A time of day without a date or time zone, such as 10:15:30.
LocalTimeis a value-based type — identity operators (==) are unreliable. UseisEquals,compareTo,isAfter, orisBefore.
getCurrentTime()returnsLocalTimeofTime(int hour, int minute)returnsLocalTimeofTimeWithSecond(int hour, int minute, int second)returnsLocalTimeofTimeWithSecondNano(int hour, int minute, int second, int nanoOfSecond)returnsLocalTimeofNanoOfDay(int nanoOfDay)returnsLocalTimeofSecondOfDay(int secondOfDay)returnsLocalTimegetMinTime()returnsLocalTime— minimum (00:00)getMAXTime()returnsLocalTime— maximum (23:59:59.999999999)getMIDNIGHT()returnsLocalTimegetNOON()returnsLocalTime
jt:LocalTime t = jt:ofTimeWithSecond(10, 15, 30);Output: toString() returns string
Component getters: getHour(), getMinute(), getSecond(), getNano()
Comparison — unlike LocalDate/LocalDateTime, these are all public here:
isEquals(Object other)returnsbooleancompareTo(LocalTime other)returnsintisAfter(LocalTime other)returnsbooleanisBefore(LocalTime other)returnsbooleanhashCode()also available
Combining with a date: atDate(LocalDate date) returns LocalDateTime — never panics
As seconds/nanoseconds of day: toSecondOfDay(), toNanoOfDay()
Arithmetic (immutable, wraps cyclically — never panics):
- Add:
plusHours,plusMinutes,plusSeconds,plusNanos - Subtract:
minusHours,minusMinutes,minusSeconds,minusNanos - e.g.
23:00+ 2 hours =01:00— stays within the same day, unlikeLocalDateTime.
Altering fields (with*, panics on invalid value instead of wrapping):
withHour, withMinute, withSecond, withNano
import ballerina/io;
import kruutteri1/java_time_utils.javatime as jt;
public function main() {
jt:LocalTime t = jt:ofTimeWithSecond(10, 15, 30);
jt:LocalTime inTwoHours = t.plusHours(2);
boolean isLater = inTwoHours.isAfter(t); // true
io:println(t.toString());
io:println(inTwoHours.toString());
jt:LocalDate d = jt:ofDate(2026, 7, 15);
jt:LocalDateTime dt = t.atDate(d);
io:println(dt.toString());
// Wraparound near midnight (stays within the same day)
jt:LocalTime lateNight = jt:ofTimeWithSecond(23, 0, 0);
jt:LocalTime wrapped = lateNight.plusHours(2);
io:println(wrapped.toString()); // 01:00:00
}Ballerina does not support method/function overloading, so this library's function names don't mirror java.time one-to-one:
- Creation functions carry a suffix indicating arity, since Ballerina can't have multiple functions named
ofin the same module.LocalDateusesofDate(...);LocalDateTimeusesofDateTime(...)/ofWithSeconds(...)/ofWithNanos(...);LocalTimeusesofTime(...)/ofTimeWithSecond(...)/ofTimeWithSecondNano(...). equalsis exposed asisEquals. Ballerina reservesequalsas a keyword-adjacent identifier; this library uses the plain, unescaped nameisEquals(Object other)on all three types instead.wait/wait2/wait3are exposed asdoWait/waitWithTimeout/waitWithTimeoutAndNanos— same reasoning, see Inherited Java Object Methods.compareTo,isAfter,isBeforeare only public onLocalTime— onLocalDateandLocalDateTimethese exist internally but aren't part of the current public API.
Functions and methods that validate their input — creation functions like ofDate, and any with* method — panic on an out-of-range value (e.g. month 13, or February 30th). They return a plain T, not T|error; invalid input triggers a runtime panic from the underlying Java DateTimeException.
Use trap to convert the panic into a catchable error:
import ballerina/io;
import kruutteri1/java_time_utils.javatime as jt;
public function main() {
jt:LocalDate|error d = trap jt:ofDate(2026, 2, 30);
if d is error {
io:println("Invalid date: ", d.message());
} else {
io:println("Created: ", d.toString());
}
}If you're confident the input is always valid (e.g. hardcoded constants), you can skip trap — but any dynamic or user-supplied input should go through this pattern to avoid an unhandled panic crashing your program.
This applies uniformly to LocalDate, LocalDateTime, and LocalTime.
All three types inherit these from java.lang.Object, mainly for low-level thread synchronization — you almost certainly don't need them in ordinary business logic.
| Method | Parameters | Description |
|---|---|---|
notify() |
— | Wakes a single thread waiting on the object's monitor |
notifyAll() |
— | Wakes all threads waiting on the object's monitor |
doWait() |
— | Waits for a notification (may return InterruptedException) |
waitWithTimeout(int timeoutMillis) |
timeoutMillis | Waits with a millisecond timeout |
waitWithTimeoutAndNanos(int timeoutMillis, int nanos) |
timeoutMillis, nanos | Waits with a timeout (ms + ns) |
hashCode() |
— | Hash code, consistent with isEquals |
jt:InterruptedException? err = d.doWait();
if err is jt:InterruptedException {
// handle interruption
}Available identically on LocalDate, LocalDateTime, and LocalTime.