19.2 Date and Time Operations
This section covers practical operations with LocalDate, LocalTime, LocalDateTime, ZonedDateTime, and OffsetDateTime, including creation, manipulation, conversion, and common patterns for date-time calculations.
LocalDate: Date Without Time
LocalDate represents a date without time or timezone information (year-month-day).
Creating LocalDate:
public class LocalDateCreation {
public void factoryMethods() {
// Current date in default timezone
LocalDate today = LocalDate.now();
// Current date in specific timezone
LocalDate tokyoToday = LocalDate.now(ZoneId.of("Asia/Tokyo"));
// Specific date
LocalDate date1 = LocalDate.of(2025, 1, 6);
LocalDate date2 = LocalDate.of(2025, Month.JANUARY, 6);
// From year and day of year
LocalDate dayOfYear = LocalDate.ofYearDay(2025, 100); // 100th day of 2025
// From epoch day (days since 1970-01-01)
LocalDate fromEpoch = LocalDate.ofEpochDay(19723); // 2024-01-01
// Parsing from string
LocalDate parsed = LocalDate.parse("2025-01-06");
LocalDate parsedCustom = LocalDate.parse("06/01/2025",
DateTimeFormatter.ofPattern("dd/MM/yyyy"));
System.out.println("Today: " + today);
System.out.println("Parsed: " + parsed);
}
}
LocalDate Operations:
public class LocalDateOperations {
public void fieldAccess() {
LocalDate date = LocalDate.of(2025, 1, 6);
// Get individual fields
int year = date.getYear();
Month month = date.getMonth();
int monthValue = date.getMonthValue(); // 1-12
int dayOfMonth = date.getDayOfMonth();
DayOfWeek dayOfWeek = date.getDayOfWeek();
int dayOfYear = date.getDayOfYear();
// Derived information
boolean isLeapYear = date.isLeapYear();
int lengthOfMonth = date.lengthOfMonth();
int lengthOfYear = date.lengthOfYear();
System.out.println(date + " is a " + dayOfWeek);
System.out.println("Leap year: " + isLeapYear);
}
public void arithmetic() {
LocalDate date = LocalDate.of(2025, 1, 15);
// Add/subtract days, weeks, months, years
LocalDate tomorrow = date.plusDays(1);
LocalDate nextWeek = date.plusWeeks(1);
LocalDate nextMonth = date.plusMonths(1);
LocalDate nextYear = date.plusYears(1);
LocalDate yesterday = date.minusDays(1);
LocalDate lastWeek = date.minusWeeks(1);
LocalDate lastMonth = date.minusMonths(1);
LocalDate lastYear = date.minusYears(1);
// Generic plus/minus with Period
LocalDate inThreeMonths = date.plus(Period.ofMonths(3));
LocalDate twoWeeksAgo = date.minus(Period.ofWeeks(2));
// With method - replace specific field
LocalDate firstOfMonth = date.withDayOfMonth(1);
LocalDate firstOfYear = date.withDayOfYear(1);
LocalDate sameMonth2026 = date.withYear(2026);
System.out.println("Tomorrow: " + tomorrow);
System.out.println("First of month: " + firstOfMonth);
}
public void comparisons() {
LocalDate date1 = LocalDate.of(2025, 1, 1);
LocalDate date2 = LocalDate.of(2025, 12, 31);
// Comparison methods
boolean isBefore = date1.isBefore(date2);
boolean isAfter = date1.isAfter(date2);
boolean isEqual = date1.isEqual(date2);
// Comparable interface
int comparison = date1.compareTo(date2); // negative if before
// Calculate period between dates
Period period = Period.between(date1, date2);
System.out.println("Period: " + period.getMonths() + " months, " +
period.getDays() + " days");
// Calculate days between
long daysBetween = ChronoUnit.DAYS.between(date1, date2);
System.out.println("Days between: " + daysBetween);
}
}
Common LocalDate Patterns:
public class LocalDatePatterns {
public LocalDate getEndOfMonth(LocalDate date) {
return date.withDayOfMonth(date.lengthOfMonth());
}
public LocalDate getStartOfMonth(LocalDate date) {
return date.withDayOfMonth(1);
}
public LocalDate getEndOfYear(LocalDate date) {
return LocalDate.of(date.getYear(), 12, 31);
}
public boolean isWeekend(LocalDate date) {
DayOfWeek day = date.getDayOfWeek();
return day == DayOfWeek.SATURDAY || day == DayOfWeek.SUNDAY;
}
public LocalDate getNextBusinessDay(LocalDate date) {
LocalDate next = date.plusDays(1);
while (isWeekend(next)) {
next = next.plusDays(1);
}
return next;
}
public List<LocalDate> getDateRange(LocalDate start, LocalDate end) {
List<LocalDate> dates = new ArrayList<>();
LocalDate current = start;
while (!current.isAfter(end)) {
dates.add(current);
current = current.plusDays(1);
}
return dates;
}
public int getQuarter(LocalDate date) {
return (date.getMonthValue() - 1) / 3 + 1;
}
public LocalDate addBusinessDays(LocalDate date, int businessDays) {
LocalDate result = date;
int added = 0;
while (added < businessDays) {
result = result.plusDays(1);
if (!isWeekend(result)) {
added++;
}
}
return result;
}
}
LocalTime: Time Without Date
LocalTime represents a time without date or timezone information (hour-minute-second-nanosecond).
Creating LocalTime:
public class LocalTimeCreation {
public void factoryMethods() {
// Current time
LocalTime now = LocalTime.now();
// Specific time
LocalTime time1 = LocalTime.of(14, 30); // 14:30
LocalTime time2 = LocalTime.of(14, 30, 45); // 14:30:45
LocalTime time3 = LocalTime.of(14, 30, 45, 123456789); // with nanos
// From seconds of day
LocalTime fromSeconds = LocalTime.ofSecondOfDay(52200); // 14:30:00
// From nanos of day
LocalTime fromNanos = LocalTime.ofNanoOfDay(52200000000000L);
// Parsing
LocalTime parsed = LocalTime.parse("14:30:45");
LocalTime parsedCustom = LocalTime.parse("2:30 PM",
DateTimeFormatter.ofPattern("h:mm a"));
// Constants
LocalTime midnight = LocalTime.MIDNIGHT; // 00:00
LocalTime noon = LocalTime.NOON; // 12:00
LocalTime minTime = LocalTime.MIN; // 00:00
LocalTime maxTime = LocalTime.MAX; // 23:59:59.999999999
System.out.println("Now: " + now);
}
}
LocalTime Operations:
public class LocalTimeOperations {
public void fieldAccess() {
LocalTime time = LocalTime.of(14, 30, 45, 123456789);
// Get fields
int hour = time.getHour(); // 0-23
int minute = time.getMinute(); // 0-59
int second = time.getSecond(); // 0-59
int nano = time.getNano(); // 0-999,999,999
// Convert to totals
long secondOfDay = time.toSecondOfDay();
long nanoOfDay = time.toNanoOfDay();
System.out.println("Second of day: " + secondOfDay);
}
public void arithmetic() {
LocalTime time = LocalTime.of(14, 30);
// Add/subtract time units
LocalTime plus1Hour = time.plusHours(1);
LocalTime plus30Min = time.plusMinutes(30);
LocalTime plus45Sec = time.plusSeconds(45);
LocalTime plusNanos = time.plusNanos(1000000);
LocalTime minus2Hours = time.minusHours(2);
LocalTime minus15Min = time.minusMinutes(15);
// Wraps around midnight
LocalTime late = LocalTime.of(23, 30);
LocalTime afterMidnight = late.plusHours(2); // 01:30
System.out.println("23:30 + 2 hours = " + afterMidnight);
// With method
LocalTime newHour = time.withHour(16);
LocalTime newMinute = time.withMinute(45);
LocalTime truncated = time.truncatedTo(ChronoUnit.MINUTES); // 14:30:00
}
public void comparisons() {
LocalTime time1 = LocalTime.of(9, 0);
LocalTime time2 = LocalTime.of(17, 0);
boolean isBefore = time1.isBefore(time2);
boolean isAfter = time1.isAfter(time2);
// Duration between times
Duration duration = Duration.between(time1, time2);
System.out.println("Work hours: " + duration.toHours() + " hours");
}
}
Common LocalTime Patterns:
public class LocalTimePatterns {
public boolean isBusinessHours(LocalTime time) {
LocalTime start = LocalTime.of(9, 0);
LocalTime end = LocalTime.of(17, 0);
return !time.isBefore(start) && time.isBefore(end);
}
public LocalTime roundToNearestMinute(LocalTime time) {
int seconds = time.getSecond();
if (seconds >= 30) {
return time.plusMinutes(1).truncatedTo(ChronoUnit.MINUTES);
} else {
return time.truncatedTo(ChronoUnit.MINUTES);
}
}
public LocalTime roundToNearest15Minutes(LocalTime time) {
int minutes = time.getMinute();
int remainder = minutes % 15;
if (remainder < 8) {
return time.withMinute(minutes - remainder).truncatedTo(ChronoUnit.MINUTES);
} else {
return time.withMinute(minutes + (15 - remainder)).truncatedTo(ChronoUnit.MINUTES);
}
}
}
LocalDateTime: Date and Time Without Timezone
LocalDateTime combines LocalDate and LocalTime without timezone information.
Creating LocalDateTime:
public class LocalDateTimeCreation {
public void factoryMethods() {
// Current date-time
LocalDateTime now = LocalDateTime.now();
// From components
LocalDateTime dt1 = LocalDateTime.of(2025, 1, 6, 14, 30);
LocalDateTime dt2 = LocalDateTime.of(2025, 1, 6, 14, 30, 45);
LocalDateTime dt3 = LocalDateTime.of(2025, 1, 6, 14, 30, 45, 123456789);
// From date and time
LocalDate date = LocalDate.of(2025, 1, 6);
LocalTime time = LocalTime.of(14, 30);
LocalDateTime combined = LocalDateTime.of(date, time);
// Convenience methods on LocalDate/LocalTime
LocalDateTime atTime = date.atTime(14, 30);
LocalDateTime atStartOfDay = date.atStartOfDay();
LocalDateTime atDate = time.atDate(date);
// Parsing
LocalDateTime parsed = LocalDateTime.parse("2025-01-06T14:30:45");
System.out.println("Now: " + now);
}
}
LocalDateTime Operations:
public class LocalDateTimeOperations {
public void fieldAccess() {
LocalDateTime dt = LocalDateTime.of(2025, 1, 6, 14, 30, 45);
// Access date components
LocalDate date = dt.toLocalDate();
int year = dt.getYear();
Month month = dt.getMonth();
int dayOfMonth = dt.getDayOfMonth();
// Access time components
LocalTime time = dt.toLocalTime();
int hour = dt.getHour();
int minute = dt.getMinute();
int second = dt.getSecond();
// Derived information
DayOfWeek dayOfWeek = dt.getDayOfWeek();
int dayOfYear = dt.getDayOfYear();
System.out.println("Date: " + date + ", Time: " + time);
}
public void arithmetic() {
LocalDateTime dt = LocalDateTime.of(2025, 1, 6, 14, 30);
// Date arithmetic
LocalDateTime tomorrow = dt.plusDays(1);
LocalDateTime nextMonth = dt.plusMonths(1);
LocalDateTime nextYear = dt.plusYears(1);
// Time arithmetic
LocalDateTime plus2Hours = dt.plusHours(2);
LocalDateTime plus30Min = dt.plusMinutes(30);
LocalDateTime plus45Sec = dt.plusSeconds(45);
// Combined arithmetic
LocalDateTime later = dt.plus(Duration.ofHours(2).plusMinutes(30));
LocalDateTime muchLater = dt.plus(Period.ofMonths(3).plusDays(15));
// With methods
LocalDateTime newDate = dt.withYear(2026).withMonth(7);
LocalDateTime newTime = dt.withHour(9).withMinute(0);
System.out.println("Original: " + dt);
System.out.println("Tomorrow same time: " + tomorrow);
}
public void conversions() {
LocalDateTime dt = LocalDateTime.now();
// Extract date and time
LocalDate date = dt.toLocalDate();
LocalTime time = dt.toLocalTime();
// Convert to zoned date-time
ZoneId zone = ZoneId.of("America/New_York");
ZonedDateTime zoned = dt.atZone(zone);
// Convert to offset date-time
ZoneOffset offset = ZoneOffset.of("-05:00");
OffsetDateTime offsetDt = dt.atOffset(offset);
System.out.println("LocalDateTime: " + dt);
System.out.println("ZonedDateTime: " + zoned);
System.out.println("OffsetDateTime: " + offsetDt);
}
}
ZonedDateTime: Date-Time with Timezone
ZonedDateTime represents a date-time with timezone, including DST rules.
Creating ZonedDateTime:
public class ZonedDateTimeCreation {
public void factoryMethods() {
// Current zoned date-time
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime nyNow = ZonedDateTime.now(ZoneId.of("America/New_York"));
// From components
ZoneId zone = ZoneId.of("Europe/Paris");
ZonedDateTime zdt1 = ZonedDateTime.of(2025, 1, 6, 14, 30, 0, 0, zone);
ZonedDateTime zdt2 = ZonedDateTime.of(
LocalDate.of(2025, 1, 6),
LocalTime.of(14, 30),
zone
);
// From LocalDateTime
LocalDateTime ldt = LocalDateTime.of(2025, 1, 6, 14, 30);
ZonedDateTime zdt3 = ldt.atZone(zone);
// From Instant
Instant instant = Instant.now();
ZonedDateTime fromInstant = instant.atZone(zone);
// Parsing
ZonedDateTime parsed = ZonedDateTime.parse("2025-01-06T14:30:00+01:00[Europe/Paris]");
System.out.println("Now: " + now);
System.out.println("Paris: " + zdt1);
}
public void ambiguousTimeHandling() {
// DST transitions can create ambiguous or invalid local times
ZoneId newYork = ZoneId.of("America/New_York");
// Spring forward: 2:00 AM becomes 3:00 AM (gap)
LocalDateTime springForward = LocalDateTime.of(2025, 3, 9, 2, 30);
ZonedDateTime resolved = springForward.atZone(newYork);
System.out.println("Spring forward: " + springForward + " -> " + resolved);
// Fall back: 2:00 AM occurs twice (overlap)
LocalDateTime fallBack = LocalDateTime.of(2025, 11, 2, 1, 30);
ZonedDateTime firstOccurrence = fallBack.atZone(newYork);
System.out.println("Fall back: " + fallBack + " -> " + firstOccurrence);
// Use ofStrict to detect ambiguity
try {
ZonedDateTime strict = ZonedDateTime.ofStrict(
springForward,
ZoneOffset.of("-05:00"),
newYork
);
} catch (DateTimeException e) {
System.out.println("Invalid time during DST gap");
}
}
}
ZonedDateTime Operations:
public class ZonedDateTimeOperations {
public void timezoneConversions() {
ZoneId newYork = ZoneId.of("America/New_York");
ZoneId tokyo = ZoneId.of("Asia/Tokyo");
ZoneId london = ZoneId.of("Europe/London");
// Create a meeting time in New York
ZonedDateTime nyMeeting = ZonedDateTime.of(
2025, 1, 6, 14, 0, 0, 0, newYork
);
// Convert to other timezones (same instant, different local time)
ZonedDateTime tokyoMeeting = nyMeeting.withZoneSameInstant(tokyo);
ZonedDateTime londonMeeting = nyMeeting.withZoneSameInstant(london);
System.out.println("New York: " + nyMeeting);
System.out.println("Tokyo: " + tokyoMeeting);
System.out.println("London: " + londonMeeting);
// All represent the same instant
Instant instant = nyMeeting.toInstant();
System.out.println("All meetings at instant: " + instant);
}
public void withZoneSameLocal() {
ZoneId newYork = ZoneId.of("America/New_York");
ZoneId london = ZoneId.of("Europe/London");
ZonedDateTime nyTime = ZonedDateTime.of(
2025, 1, 6, 14, 0, 0, 0, newYork
);
// Keep local time but change zone (different instant!)
ZonedDateTime londonSameLocal = nyTime.withZoneSameLocal(london);
System.out.println("NY 14:00: " + nyTime);
System.out.println("London 14:00: " + londonSameLocal);
// These are different instants
Duration difference = Duration.between(
nyTime.toInstant(),
londonSameLocal.toInstant()
);
System.out.println("Time difference: " + difference.toHours() + " hours");
}
public void offsetInformation() {
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("America/New_York"));
// Get current offset from UTC
ZoneOffset offset = zdt.getOffset();
int totalSeconds = offset.getTotalSeconds();
System.out.println("Current offset: " + offset);
System.out.println("Total seconds: " + totalSeconds);
// Check if currently in DST
ZoneRules rules = zdt.getZone().getRules();
boolean isDst = rules.isDaylightSavings(zdt.toInstant());
System.out.println("In DST: " + isDst);
}
}
OffsetDateTime: Date-Time with Fixed Offset
OffsetDateTime represents a date-time with a fixed UTC offset, without timezone rules.
Creating OffsetDateTime:
public class OffsetDateTimeCreation {
public void factoryMethods() {
// Current offset date-time
OffsetDateTime now = OffsetDateTime.now();
OffsetDateTime utcNow = OffsetDateTime.now(ZoneOffset.UTC);
// From components
ZoneOffset offset = ZoneOffset.of("+05:30");
OffsetDateTime odt1 = OffsetDateTime.of(2025, 1, 6, 14, 30, 0, 0, offset);
OffsetDateTime odt2 = OffsetDateTime.of(
LocalDate.of(2025, 1, 6),
LocalTime.of(14, 30),
offset
);
// From LocalDateTime
LocalDateTime ldt = LocalDateTime.of(2025, 1, 6, 14, 30);
OffsetDateTime odt3 = ldt.atOffset(offset);
// From Instant
Instant instant = Instant.now();
OffsetDateTime fromInstant = instant.atOffset(offset);
// Parsing
OffsetDateTime parsed = OffsetDateTime.parse("2025-01-06T14:30:00+05:30");
System.out.println("Now: " + now);
System.out.println("With +05:30: " + odt1);
}
public void conversionExamples() {
OffsetDateTime odt = OffsetDateTime.now();
// Convert to other types
ZonedDateTime zdt = odt.toZonedDateTime();
LocalDateTime ldt = odt.toLocalDateTime();
LocalDate date = odt.toLocalDate();
LocalTime time = odt.toLocalTime();
Instant instant = odt.toInstant();
OffsetTime offsetTime = odt.toOffsetTime();
// Change offset (adjusts local time to maintain instant)
ZoneOffset newOffset = ZoneOffset.of("+08:00");
OffsetDateTime differentOffset = odt.withOffsetSameInstant(newOffset);
System.out.println("Original: " + odt);
System.out.println("Different offset: " + differentOffset);
}
}
Choosing the Right Type
Decision Matrix:
public class TypeSelection {
public void storageRecommendations() {
// For database storage: Use Instant
Instant timestamp = Instant.now();
// Store as: BIGINT (epoch seconds) or TIMESTAMP WITH TIME ZONE
// For user display: Convert to ZonedDateTime
ZoneId userZone = ZoneId.of("America/New_York");
ZonedDateTime displayTime = timestamp.atZone(userZone);
// For API communication: Use OffsetDateTime (ISO-8601)
OffsetDateTime apiTime = OffsetDateTime.now();
String json = String.format("{\"timestamp\":\"%s\"}", apiTime);
}
public void useCaseExamples() {
// Use LocalDate for: birthdays, holidays, due dates
LocalDate birthday = LocalDate.of(1990, 5, 15);
LocalDate dueDate = LocalDate.now().plusDays(30);
// Use LocalTime for: daily schedules, opening hours
LocalTime openingTime = LocalTime.of(9, 0);
LocalTime closingTime = LocalTime.of(17, 0);
// Use LocalDateTime for: log timestamps (same timezone system)
LocalDateTime logEntry = LocalDateTime.now();
// Use ZonedDateTime for: user appointments, meetings across zones
ZonedDateTime meeting = ZonedDateTime.of(
2025, 1, 15, 14, 0, 0, 0,
ZoneId.of("America/New_York")
);
// Use Instant for: event timestamps, audit trails, database storage
Instant eventTime = Instant.now();
// Use OffsetDateTime for: REST API, JSON serialization
OffsetDateTime apiTimestamp = OffsetDateTime.now();
}
}
Summary
Date and time operations in Java provide:
- LocalDate: Date-only operations (birthdays, due dates, holidays)
- LocalTime: Time-only operations (schedules, opening hours)
- LocalDateTime: Combined date-time without timezone (logs, local events)
- ZonedDateTime: Timezone-aware operations (meetings, appointments with DST)
- OffsetDateTime: Fixed offset operations (API timestamps, serialization)
- Type Selection: Choose based on storage, display, and timezone requirements
Understanding these types enables correct temporal handling across all application scenarios.