Groovy Dates And Times Cheat Sheet
Java has had a Date
class from the very beginning and Groovy supports using it and several related classes like Calendar
. Throughout this blog post we refer to those classes as the legacy date classes.
Groovy enhances the experience of using the legacy date classes with simpler mechanisms for formatting, parsing and extracting fields from the related classes.
Since Java 8, the JDK has included the JSR-310 Date Time API. We refer to these classes as the new date classes. The new date classes remove many limitations of the legacy date classes and bring some greatly appreciated additional consistency. Groovy provides similar enhancements for the new date classes too.
Groovy's enhancements for the legacy date classes are in the groovy-dateutil
module (prior to Groovy 2.5, this functionality was built in to the core module). The groovy-datetime
module has the enhancements for the new date classes. You can include a dependency to this module in your build file or reference the groovy-all
pom dependency. Both modules are part of a standard Groovy install.
The next few sections illustrate common date and time tasks and the code to perform them using the new and legacy classes with Groovy enhancements in numerous places.
Please note: that some of the formatting commands are Locale dependent and the output may vary slightly if you run these examples yourself.
Representing the current date/time
The legacy date classes have an abstraction which includes date and time. If you are only interested in one of those two aspects, you simply ignore the other aspect. The new date classes allow you to have date-only, time-only and date-time representations.
The examples create instances representing the current date and/or time. Various information is extracted from the instances and they are printed in various ways. Some of the examples use the SV
macro which prints the name and string value of one or more variables.
task | java.time | legacy |
---|---|---|
current date and time |
println LocalDateTime.now() 2022-10-24T12:40:02.218130200 2022-10-24T02:40:02.223131Z |
println new Date() Mon Oct 24 12:40:02 AEST 2022 Mon Oct 24 12:40:02 AEST 2022 |
day of current year & day of current month |
println LocalDateTime.now().dayOfYear 297 24 |
println Calendar.instance[DAY_OF_YEAR] 297 24 |
extract today's year, month & day |
var now = LocalDate.now() // or LocalDateTime now.year=2022, now.monthValue=10, now.dayOfMonth=24 Today is 2022 10 24 |
var now = Calendar.instance Today is 2022 10 24 Today is 2022 10 24 |
alternatives to print today |
println now.format("'Today is 'YYYY-MM-dd") Today is 2022-10-24 Today is 2022-10-24 |
println now.format("'Today is 'YYYY-MM-dd") Today is 2022-10-24 Today is 2022-10-24 |
extract parts of current time |
now = LocalTime.now() // or LocalDateTime now.hour=12, now.minute=40, now.second=2 The time is 12:40:02 |
(H, M, S) = now[HOUR_OF_DAY, MINUTE, SECOND] H=12, M=40, S=2 The time is 12:40:02 |
alternatives to print time |
println now.format("'The time is 'HH:mm:ss") The time is 12:40:02 The time is 12:40:02 |
println now.format("'The time is 'HH:mm:ss") The time is 12:40:02 The time is 12:40:02 |
Processing times
The new date classes have a LocalTime
class specifically for representing time-only quantities. The legacy date classes don't have such a purpose-built abstraction; you essentially just ignore the day, month, and year parts of a date. The java.sql.Time
class could be used as an alterative but rarely is. The Java documentation comparing the new date classes to their legacy equivalents, talks about
using GregorianCalendar
with the date set to the epoch value of 1970-01-01
as an approximation of the LocalTime
class. We'll follow that approach here to provide a comparison but we strongly recommend upgrading to the
new classes if you need to represent time-only values or use the Joda-Time library on JDK versions prior to 8.
The examples look at representing a minute before and after midnight, and some times at which you might eat your meals. For the meals, as well as printing various values, we might be interested in calculating new times in terms of existing times, e.g. lunch and dinner are 7 hours apart.
task | java.time | legacy |
---|---|---|
one min after midnight |
LocalTime.of(0, 1).with { 00:01 12:01 am 0:01 am |
Calendar.instance.with { 00:01 12:01 am 0:01 am |
one min before midnight |
LocalTime.of(23, 59).with { 23:59 11:59 pm 11:59 pm |
Calendar.instance.with { 23:59 11:59 pm 11:59 pm |
meal times |
var breakfast = LocalTime.of(7, 30) |
var breakfast = Date.parse('hh:mm', '07:30') |
Processing dates
To represent date-only information with the legacy date classes, you set the time aspects to zero, or simply ignore them. Alternatively, you could consider the less commonly used java.sql.Date
class. The new date classes have the special LocalDate
class for this purpose which we highly recommend.
The examples create dates for Halloween and Melbourne Cup day (a public holiday in the Australia state of Victoria). We look at various properties of those two dates.
task | java.time | legacy |
---|---|---|
holidays |
var halloween22 = LocalDate.of(2022, 10, 31) |
var halloween21 = Date.parse('dd/MM/yyyy', '31/10/2021') |
Processing date and time combinations
The new date classes use LocalDateTime
to represent a quantity with both date and time aspects. Many of the methods seen earlier will also be applicable here.
The examples show creating and printing a representation of lunch on Melbourne Cup day.
task | java.time | legacy |
---|---|---|
holidays |
var melbourneCupLunch = LocalDateTime.of(2022, 11, 1, 12, 30) |
var melbourneCupLunch = new GregorianCalendar(2022, 10, 1, 12, 30).time |
Processing zoned date and times
The legacy date classes have the concept of a TimeZone
, predominantly used by the Calendar class. The new date classes has a similar concept but uses the ZoneId
, ZoneOffset
, and ZonedDateTime
classes (among others).
The examples show various properties of zones and show that during the Melbourne cup breakfast, it would still be the night before (Halloween) in Los Angeles. They also show that those two zones are 18 hours apart at that time of the year.
task | java.time | legacy |
---|---|---|
holidays |
var aet = ZoneId.of('Australia/Sydney') |
var aet = TimeZone.getTimeZone('Australia/Sydney') |
Other useful classes
The new date classes offer a few more useful classes. Here are some of the common ones:
OffsetDateTime
- likeZonedDateTime
but with just an offset from UTC rather than a full time-zoneInstant
- likeOffsetDateTime
but tied to UTCYearMonth
- like aLocalDate
but with no day componentMonthDay
- like aLocalDate
but with no year componentPeriod
- used to represent periods of time, e.g.Period.ofDays(14)
,Period.ofYears(2)
; see also theLocalDate
example above.Duration
- a time-based amount of time, e.g.Duration.ofSeconds(30)
,Duration.ofHours(7)
; see also theLocalTime
example above.
Conversions
It is useful to convert between the new and legacy classes. Some useful conversion methods are shown below with Groovy enhancements shown in blue.
From | Conversion method/property |
---|---|
GregorianCalendar |
toInstant() toZonedDateTime() from(ZonedDateTime) |
Calendar |
toInstant()
toZonedDateTime()
toOffsetDateTime()
toLocalDateTime()
toLocalDate()
toLocalTime()
toOffsetTime()
toDayOfWeek()
toYear()
toYearMonth()
toMonth()
toMonthDay()
zoneOffset
zoneId
|
Date |
toInstant()
from(Instant)
toZonedDateTime()
toOffsetDateTime()
toLocalDateTime()
toLocalDate()
toLocalTime()
toOffsetTime()
toDayOfWeek()
toYear()
toYearMonth()
toMonth()
toMonthDay()
zoneOffset
zoneId
|
ZonedDateTime OffsetDateTime LocalDateTime LocalDate LocalTime |
toDate()
toCalendar()
|
SimpleDateFormat patterns
We saw several examples above using the format
and parse
methods. For the legacy date classes, numerous Groovy enhancements delegate to SimpleDateFormat
.
This class represents date/time formats using pattern strings. These are special letters to represent some time or date component mixed with escaped literal strings. The special letters are often repeated to represent the minimum size field for number components and whether the full or an abbreviated form is used for other components.
As an example, for the U.S. locale and U.S. Pacific Time time zone, the following pattern:
yyyy.MM.dd G 'at' HH:mm:ss z
would apply to the following text:
2001.07.04 AD at 12:08:56 PDT
Letter | Description |
---|---|
G | Era designator AD |
y | Year 1996; 96 |
Y | Week year (similar to year but allotted by weeks; the first/last few days of a year might be allotted to finish/start the last/previous week) |
M | Month in year (context sensitive) July; Jul; 07 |
L | Month in year (standalone form) July; Jul; 07 |
w | Week in year 27 |
W | Week in month 2 |
D | Day in year 189 |
d | Day in month 10 |
F | Day of week in month 2 |
E | Day name in week Tuesday; Tue |
u | Day number of week (1 = Monday, ..., 7 = Sunday) |
a | Am/pm marker PM |
H | Hour in day (0-23) 0 |
k | Hour in day (1-24) 24 |
K | Hour in am/pm (0-11) 0 |
h | Hour in am/pm (1-12) 12 |
m | Minute in hour 30 |
s | Second in minute 55 |
S | Millisecond 978 |
z | Time zone Pacific Standard Time; PST; GMT-08:00 |
Z | Time zone (RFC 822) -0800 |
X | Time zone (ISO 8601) -08; -0800; -08:00 |
' | to escape text put a single quote on either side |
'' | two single quotes for a literal single quote ' |
DateTimeFormatter patterns
Groovy's format
and parse
enhancements for the new date classes delegate to the DateTimeFormatter
class. It's behavior is similar to what we saw for SimpleDateFormat
but with slightly different conversion letters:
Conversion suffix | Description |
---|---|
G | era AD |
u | year 2004; 04 |
y | year-of-era 2004; 04 |
D | day-of-year 189 |
M/L | month-of-year 7; 07; Jul; July; J |
d | day-of-month 10 |
Q/q | quarter-of-year 3; 03; Q3; 3rd quarter |
Y | week-based-year 1996; 96 |
w | week-of-week-based-year 27 |
W | week-of-month 4 |
E | day-of-week Tue; Tuesday; T |
e/c | localized day-of-week 2; 02; Tue; Tuesday; T |
F | week-of-month 3 |
a | am-pm-of-day PM |
h | clock-hour-of-am-pm (1-12) 12 |
K | hour-of-am-pm (0-11) 0 |
k | clock-hour-of-am-pm (1-24) 0 |
H | hour-of-day (0-23) 0 |
m | minute-of-hour 30 |
s | second-of-minute 55 |
S | fraction-of-second 978 |
A | milli-of-day 1234 |
n | nano-of-second 987654321 |
N | nano-of-day 1234000000 |
V | time-zone ID America/Los_Angeles; Z; -08:30 |
z | time-zone name Pacific Standard Time; PST |
O | localized zone-offset GMT+8; GMT+08:00; UTC-08:00; |
X | zone-offset 'Z' for zero Z; -08; -0830; -08:30; -083015; -08:30:15; |
x | zone-offset +0000; -08; -0830; -08:30; -083015; -08:30:15; |
Z | zone-offset +0000; -0800; -08:00; |
p | pad next |
' | to escape text put a single quote on either side |
'' | two single quotes for a literal single quote ' |
Localized Patterns
JDK19 adds the ofLocalizedPattern(String requestedTemplate)
method. The requested template is one or more regular expression pattern symbols ordered from the largest to the smallest unit, and
consisting of the following patterns:
"G{0,5}" + // Era "y*" + // Year "Q{0,5}" + // Quarter "M{0,5}" + // Month "w*" + // Week of Week Based Year "E{0,5}" + // Day of Week "d{0,2}" + // Day of Month "B{0,5}" + // Period/AmPm of Day "[hHjC]{0,2}" + // Hour of Day/AmPm (refer to LDML for 'j' and 'C') "m{0,2}" + // Minute of Hour "s{0,2}" + // Second of Minute "[vz]{0,4}" // Zone
The requested template is mapped to the closest of available localized format as defined by the Unicode LDML specification. Here is an example of usage:
var now = ZonedDateTime.now()
var columns = '%7s | %10s | %10s | %10s | %14s%n'
printf columns, 'locale', 'GDK', 'custom', 'local', 'both'
[locale('en', 'US'),
locale('ro', 'RO'),
locale('vi', 'VN')].each { locale ->
Locale.default = locale
var gdk = now.format('y-MM-dd')
var custom = now.format(ofPattern('y-MM-dd'))
var local = now.format(ofLocalizedDate(SHORT))
var both = now.format(ofLocalizedPattern('yMM'))
printf columns, locale, gdk, custom, local, both
}
Which has this output:
locale | GDK | custom | local | both
en_US | 2022-12-18 | 2022-12-18 | 12/18/22 | 12/2022
ro_RO | 2022-12-18 | 2022-12-18 | 18.12.2022 | 12.2022
vi_VN | 2022-12-18 | 2022-12-18 | 18/12/2022 | tháng 12, 2022
Example credit: this example from Nicolai Parlog.
Formatter formats
The java.util.Formatter
class is a base class in Java for various kinds of formatting. It can be used directly, via String.format
, parse
, printf
, or Groovy's sprintf
.
We saw several examples of using printf
and parse
formatting in the above examples.
The Formatter
class has methods which take a format string as its first argument and zero or more additional arguments.
The format string typically has one or more format specifiers (starting with a percent character) which
indicate that a formatted version of one of the additional arguments should be placed into the string at that point.
The general form of a format specifier is:
%[argument_index$][flag][width][.precision]conversion
Most of the parts are optional. The argument_index
part is only used when referencing
one of the additional arguments more than once (or out of order). The precision
part
is only used for floating point numbers. The flag
part is used to indicate always include sign(+),
zero-padding(0), locale-specific comma delimiters(,), and left justification(-).
The width
indicates the minimum number of characters for the output.
The conversion
indicates how the argument should be processed, e.g. as a numeric field, a date,
a special character, or some other special processing. Upper and lowercase variants exist for most conversions
which, for the uppercase variant, will call toUpperCase
after the conversion is complete.
Conversion | Description |
---|---|
'b', 'B' | Treat as a boolean or false if null |
'h', 'H' | Output the arguments hashcode as a hex string |
's', 'S' | Treat as a String |
'c', 'C' | Treat as a Unicode character |
'd' | Treat as a decimal integer |
'o' | Treat as an octal integer |
'x', 'X' | Treat as a hexadecimal integer |
'e', 'E' | Treat as a decimal number in scientific notation |
'f' | Treat as a floating point number |
'g', 'G' | Treat as a floating point in either decimal or scientific notation |
'a', 'A' | Treat as a hexadecimal floating-point number |
't', 'T' | Treat as the prefix for a date/time conversion |
'%' | A literal percent |
'n' | A line separator |
When the date/time prefix is used, additional suffixes are applicable.
For formatting times:
Conversion suffix | Description |
---|---|
'H' | Hour of the day for the 24-hour clock as two digits 00 - 23 |
'I' | Hour for the 12-hour clock as two digits 01 - 12 |
'k' | Hour of the day for the 24-hour clock 0 - 23 |
'l' | Hour for the 12-hour clock 1 - 12 |
'M' | Minute within the hour as two digits 00 - 59 |
'S' | Seconds within the minute as two digits 00 - 60 ("60" is used for leap seconds) |
'L' | Millisecond within the second as three digits 000 - 999 |
'N' | Nanosecond within the second as nine digits 000000000 - 999999999 |
'p' | Locale-specific morning or afternoon marker in lower case, am or pm (The conversion prefix 'T' forces this output to upper case) |
'z' | RFC 822 style numeric time zone offset from GMT -0800 (Adjusted as needed for Daylight Saving Time) |
'Z' | Abbreviated time zone |
's' | Seconds since the beginning of the epoch starting at 1 January 1970 00:00:00 UTC |
'Q' | Milliseconds since the beginning of the epoch starting at 1 January 1970 00:00:00 UTC |
For formatting dates:
Conversion suffix | Description |
---|---|
'B' | Locale-specific full month name January |
'b', 'h' | Locale-specific abbreviated month name Jan |
'A' | Locale-specific full name of the day of the week Sunday |
'a' | Locale-specific short name of the day of the week Sun |
'C' | First two digits of four-digit year 00 - 99 |
'Y' | Year as four digits 0092 |
'y' | Last two digits of the year 00 - 99 |
'j' | Day of year as three digits 001 - 366 |
'm' | Month as two digits 01 - 13 |
'd' | Day of month as two digits 01 - 31 |
'e' | Day of month 1 - 31 |
For formatting date/time compositions:
Conversion suffix | Description |
---|---|
'R' | Time formatted for the 24-hour clock as "%tH:%tM" |
'T' | Time formatted for the 24-hour clock as "%tH:%tM:%tS" |
'r' | Time formatted for the 12-hour clock as "%tI:%tM:%tS %Tp" The location of the morning or afternoon marker ('%Tp') may be locale-dependent. |
'D' | Date formatted as "%tm/%td/%ty" |
'F' | ISO 8601 date formatted as "%tY-%tm-%td" |
'c' | Date and time formatted as "%ta %tb %td %tT %tZ %tY" Sun Jul 21 15:17:00 EDT 1973 |
Further information
- Java 8 LocalDate, LocalDateTime, Instant tutorial
- Introduction to the Java 8 Date/Time API
- A guide to SimpleDateFormat
- Joda-Time website
- Guidelines for Date-Time/legacy date interoperability
- Source code: examples for new date classes
- Source code: examples for legacy date classes