Groovy extensions for working with Java 8+ date/time types, providing convenient methods for temporal operations and transformations
—
Functionality for iterating over temporal ranges with support for custom units, step sizes, and calculating periods or durations between temporal objects.
Core iteration functionality that works with all Temporal subtypes.
/**
* Iterates from this to the target Temporal, inclusive, incrementing by one default unit each iteration.
* Default units: DAYS for dates, MONTHS for YearMonth, YEARS for Year, SECONDS for others.
*/
void upto(Temporal from, Temporal to, Closure closure)
/**
* Iterates from this to the target Temporal, inclusive, incrementing by one specified unit each iteration.
*/
void upto(Temporal from, Temporal to, TemporalUnit unit, Closure closure)
/**
* Iterates from this to the target Temporal, inclusive, decrementing by one default unit each iteration.
*/
void downto(Temporal from, Temporal to, Closure closure)
/**
* Iterates from this to the target Temporal, inclusive, decrementing by one specified unit each iteration.
*/
void downto(Temporal from, Temporal to, TemporalUnit unit, Closure closure)
/**
* Returns a TemporalAmount (Duration or Period) between this and another Temporal.
* Returns Period for dates, Duration for times.
*/
TemporalAmount rightShift(Temporal self, Temporal other)Generic field access operations for all temporal objects that implement TemporalAccessor.
/**
* Supports the subscript operator for accessing temporal fields.
* Equivalent to calling getLong(TemporalField).
*/
long getAt(TemporalAccessor self, TemporalField field)Generic unit access operations for temporal amounts (Duration and Period).
/**
* Supports the subscript operator for accessing temporal units.
* Equivalent to calling get(TemporalUnit).
*/
long getAt(TemporalAmount self, TemporalUnit unit)Usage Examples:
import java.time.*
import java.time.temporal.ChronoUnit
import java.time.temporal.ChronoField
// Date iteration with default unit (days)
def start = LocalDate.of(2024, 1, 1)
def end = start + 5
start.upto(end) { date ->
println "${date} is ${date.dayOfWeek}"
}
// Date iteration with custom unit
start.upto(end, ChronoUnit.DAYS) { date ->
println "Processing: ${date}"
}
// Time iteration (default unit is seconds)
def startTime = LocalTime.of(10, 0)
def endTime = startTime + 5 // 5 seconds later
startTime.upto(endTime) { time ->
println time
}
// Year iteration (default unit is years)
def startYear = Year.of(2020)
def endYear = Year.of(2024)
startYear.upto(endYear) { year ->
println "Year: ${year.value}"
}
// Downto iteration
end.downto(start) { date ->
println "Counting down: ${date}"
}
// Period calculation using right shift operator
def period = start >> end // Returns Period
println "Period: ${period.days} days"
// Duration calculation
def duration = startTime >> endTime // Returns Duration
println "Duration: ${duration.seconds} seconds"
// TemporalAccessor field access using subscript operator
def now = LocalDateTime.now()
def hourOfDay = now[ChronoField.HOUR_OF_DAY] // Get hour field
def dayOfYear = now[ChronoField.DAY_OF_YEAR] // Get day of year
def month = now[ChronoField.MONTH_OF_YEAR] // Get month
println "Current time: ${now}"
println "Hour of day: ${hourOfDay}"
println "Day of year: ${dayOfYear}"
println "Month: ${month}"
// TemporalAmount unit access using subscript operator
def period = Period.of(2, 3, 15) // 2 years, 3 months, 15 days
def years = period[ChronoUnit.YEARS] // Get years component
def months = period[ChronoUnit.MONTHS] // Get months component
def days = period[ChronoUnit.DAYS] // Get days component
println "Period: ${period}"
println "Years: ${years}"
println "Months: ${months}"
println "Days: ${days}"
def duration2 = Duration.ofHours(5).plusMinutes(30)
def hours = duration2[ChronoUnit.HOURS] // Get hours component
def minutes = duration2[ChronoUnit.MINUTES] // Get total minutes
println "Duration: ${duration2}"
println "Total hours: ${hours}"
println "Total minutes: ${minutes}"Specialized iteration methods for date-based temporal types.
/**
* Returns a LocalDate one day after this date.
*/
LocalDate next(LocalDate self)
/**
* Returns a LocalDate one day before this date.
*/
LocalDate previous(LocalDate self)
/**
* Returns a Period between this date and another date.
*/
Period rightShift(LocalDate self, LocalDate other)Usage Examples:
import java.time.*
def today = LocalDate.now()
def dates = []
// Collect dates using next() method
def current = today
5.times {
dates << current
current = current.next()
}
// Create date ranges for iteration
def startDate = LocalDate.of(2024, 1, 1)
def endDate = LocalDate.of(2024, 1, 7)
// Standard Groovy range operator works with dates
(startDate..endDate).each { date ->
println "${date}: ${date.dayOfWeek}"
}
// Calculate period between dates
def period = startDate >> endDate
println "Difference: ${period.days} days"Iteration support for year and month-based temporal types.
/**
* Returns a Year one year after this year.
*/
Year next(Year self)
/**
* Returns a Year one year before this year.
*/
Year previous(Year self)
/**
* Returns a Period between the first day of this year and the first day of the target year.
*/
Period rightShift(Year self, Year other)
/**
* Returns a YearMonth one month after this year-month.
*/
YearMonth next(YearMonth self)
/**
* Returns a YearMonth one month before this year-month.
*/
YearMonth previous(YearMonth self)
/**
* Returns a Period between the first day of this year-month and the first day of the target year-month.
*/
Period rightShift(YearMonth self, YearMonth other)Usage Examples:
import java.time.*
// Year iteration
def startYear = Year.of(2020)
def endYear = Year.of(2024)
startYear.upto(endYear) { year ->
println "Processing year: ${year.value}"
println "Leap year: ${year.isLeap()}"
}
// YearMonth iteration
def startMonth = YearMonth.of(2024, 1)
def endMonth = YearMonth.of(2024, 6)
startMonth.upto(endMonth) { yearMonth ->
println "${yearMonth.month} ${yearMonth.year}: ${yearMonth.lengthOfMonth()} days"
}
// Calculate periods
def yearPeriod = startYear >> endYear
def monthPeriod = startMonth >> endMonth
println "Years between: ${yearPeriod.years}"
println "Months between: ${monthPeriod.months}"Advanced iteration with custom temporal units and step sizes.
Usage Examples:
import java.time.*
import java.time.temporal.ChronoUnit
def start = LocalDateTime.of(2024, 1, 1, 0, 0)
def end = start.plusDays(2)
// Iterate by hours
start.upto(end, ChronoUnit.HOURS) { dateTime ->
println dateTime.format('yyyy-MM-dd HH:mm')
}
// Iterate by 6-hour increments (not directly supported, but can be achieved)
def current = start
while (current <= end) {
println current.format('yyyy-MM-dd HH:mm')
current = current.plus(6, ChronoUnit.HOURS)
}
// Week-based iteration
def monday = LocalDate.of(2024, 1, 1) // Assume this is a Monday
def endOfMonth = LocalDate.of(2024, 1, 31)
monday.upto(endOfMonth, ChronoUnit.WEEKS) { date ->
println "Week starting: ${date}"
}Integration with Groovy's range operators for natural iteration syntax.
Usage Examples:
import java.time.*
def start = LocalDate.of(2024, 1, 1)
def end = start + 10
// Using Groovy range operator (works because of next/previous methods)
(start..end).each { date ->
if (date.dayOfWeek == DayOfWeek.SUNDAY) {
println "Sunday: ${date}"
}
}
// Using step with ranges (for every other day)
(start..end).step(2) { date ->
println "Every other day: ${date}"
}
// Reverse ranges
(end..start).each { date ->
println "Counting down: ${date}"
}Important behavior and limitations for temporal iteration.
Exceptions Thrown:
GroovyRuntimeException - If the start value is later than the end value for upto()GroovyRuntimeException - If the start value is earlier than the end value for downto()GroovyRuntimeException - If the temporal types don't matchGroovyRuntimeException - If temporal type doesn't support the specified unitUsage Examples:
import java.time.*
def start = LocalDate.of(2024, 1, 5)
def end = LocalDate.of(2024, 1, 1) // Earlier than start
try {
// This will throw GroovyRuntimeException
start.upto(end) { date ->
println date
}
} catch (GroovyRuntimeException e) {
println "Cannot iterate backwards with upto(): ${e.message}"
}
// Use downto() instead
start.downto(end) { date ->
println "Going backwards: ${date}"
}
// Type mismatch will also cause errors
def date = LocalDate.now()
def time = LocalTime.now()
try {
// This will throw GroovyRuntimeException
date.upto(time) { /* won't work */ }
} catch (GroovyRuntimeException e) {
println "Type mismatch: ${e.message}"
}Install with Tessl CLI
npx tessl i tessl/maven-org-codehaus-groovy--groovy-datetime