blob: 3853bd074eb7675ded8c10b90aa00a17a1197a9f [file] [log] [blame]
//////////////////////////////////////////
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
//////////////////////////////////////////
= Working with Date/Time types
The `groovy-datetime` module supports numerous extensions for working with
the http://www.oracle.com/technetwork/articles/java/jf14-date-time-2125367.html[Date/Time API]
introduced in Java 8. This documentation refers to the data types defined by this API as
"JSR 310 types."
== Formatting and parsing
A common use case when working with date/time types is to convert them to Strings (formatting)
and from Strings (parsing). Groovy provides these additional formatting methods:
[cols="1,1,1" options="header"]
|====
| Method
| Description
| Example
| `getDateString()`
| For `LocalDate` and `LocalDateTime`, formats with
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_LOCAL_DATE[`DateTimeFormatter.ISO_LOCAL_DATE`]
| `2018-03-10`
|
| For `OffsetDateTime`, formats with
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_OFFSET_DATE[`DateTimeFormatter.ISO_OFFSET_DATE`]
| `2018-03-10+04:00`
|
| For `ZonedDateTime`, formats with
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_LOCAL_DATE[`DateTimeFormatter.ISO_LOCAL_DATE`]
and appends the `ZoneId` short name
| `2018-03-10EST`
| `getDateTimeString()`
| For `LocalDateTime`, formats with
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_LOCAL_DATE_TIME[`DateTimeFormatter.ISO_LOCAL_DATE_TIME`]
| `2018-03-10T20:30:45`
|
| For `OffsetDateTime`, formats with
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_OFFSET_DATE_TIME[`DateTimeFormatter.ISO_OFFSET_DATE_TIME`]
| `2018-03-10T20:30:45+04:00`
|
| For `ZonedDateTime`, formats with
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_LOCAL_DATE_TIME[`DateTimeFormatter.ISO_LOCAL_DATE_TIME`]
and appends the `ZoneId` short name
| `2018-03-10T20:30:45EST`
| `getTimeString()`
| For `LocalTime` and `LocalDateTime`, formats with
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_LOCAL_TIME[`DateTimeFormatter.ISO_LOCAL_TIME`]
| `20:30:45`
|
| For `OffsetTime` and `OffsetDateTime`, formats with
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_OFFSET_TIME[`DateTimeFormatter.ISO_OFFSET_TIME`]
formatter
| `20:30:45+04:00`
|
| For `ZonedDateTime`, formats with
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_LOCAL_TIME[`DateTimeFormatter.ISO_LOCAL_TIME`]
and appends the `ZoneId` short name
| `20:30:45EST`
| `format(FormatStyle style)`
| For `LocalTime` and `OffsetTime`, formats with
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ofLocalizedTime-java.time.format.FormatStyle-[`DateTimeFormatter.ofLocalizedTime(style)`]
| `4:30 AM` (with style `FormatStyle.SHORT`, e.g.)
|
| For `LocalDate`, formats with
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ofLocalizedDate-java.time.format.FormatStyle-[`DateTimeFormatter.ofLocalizedDate(style)`]
| `Saturday, March 10, 2018` (with style `FormatStyle.FULL`, e.g.)
|
| For `LocalDateTime`, `OffsetDateTime`, and `ZonedDateTime` formats with
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ofLocalizedDateTime-java.time.format.FormatStyle-[`DateTimeFormatter.ofLocalizedDateTime(style)`]
| `Mar 10, 2019 4:30:45 AM` (with style `FormatStyle.MEDIUM`, e.g.)
| `format(String pattern)`
| Formats with
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ofPattern-java.lang.String-[`DateTimeFormatter.ofPattern(pattern)`]
| `03/10/2018` (with pattern `'MM/dd/yyyy', e.g.)
|====
For parsing, Groovy adds a static `parse` method to many of the JSR 310 types. The method
takes two arguments: the value to be formatted and the pattern to use. The pattern is
defined by the
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html[`java.time.format.DateTimeFormatter` API].
As an example:
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=static_parsing,indent=0]
-------------------------------------
Note that these `parse` methods have a different argument ordering than the static
`parse` method Groovy added to `java.util.Date`.
This was done to be consistent with the existing `parse` methods of the Date/Time API.
== Manipulating date/time
=== Addition and subtraction
`Temporal` types have `plus` and `minus` methods for adding or subtracting a provided
`java.time.temporal.TemporalAmount` argument. Because Groovy maps the `+` and `-` operators
to single-argument methods of these names, a more natural expression syntax can be used to add and subtract.
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=plus_minus_period,indent=0]
-------------------------------------
Groovy provides additional `plus` and `minus` methods that accept an integer argument,
enabling the above to be rewritten more succinctly:
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=localdate_plus_minus_integer,indent=0]
-------------------------------------
The unit of these integers depends on the JSR 310 type operand. As evident above,
integers used with `ChronoLocalDate` types like `LocalDate` have a unit of
https://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoUnit.html#DAYShttp://days[days].
Integers used with `Year` and `YearMonth` have a unit of
https://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoUnit.html#YEARS[years] and
https://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoUnit.html#MONTHS[months], respectively.
All other types have a unit of
https://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoUnit.html#SECONDS[seconds],
such as `LocalTime`, for instance:
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=localtime_plus_minus_integer,indent=0]
-------------------------------------
=== Multiplication and division
The `*` operator can be used to multiply `Period` and `Duration` instances by an
integer value; the `/` operator can be used to divide `Duration` instances by an integer value.
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=multiply_divide,indent=0]
-------------------------------------
=== Incrementing and decrementing
The `++` and `--` operators can be used increment and decrement date/time values by one unit. Since the JSR 310 types
are immutable, the operation will create a new instance with the incremented/decremented value and reassign it to the
reference.
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=next_previous,indent=0]
-------------------------------------
=== Negation
The `Duration` and `Period` types represent a negative or positive length of time.
These can be negated with the unary `-` operator.
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=duration_negation,indent=0]
-------------------------------------
== Interacting with date/time values
=== Property notation
The
https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAccessor.html#getLong-java.time.temporal.TemporalField-[`getLong(TemporalField)`]
method of `TemporalAccessor` types (e.g. `LocalDate`,
`LocalTime`, `ZonedDateTime`, etc.) and the
https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAmount.html#get-java.time.temporal.TemporalUnit-[`get(TemporalUnit)`]
method of `TemporalAmount` types (namely `Period` and `Duration`), can be invoked with
Groovy's property notation. For example:
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=property_notation,indent=0]
-------------------------------------
=== Ranges, `upto`, and `downto`
The JSR 310 types can be used with the <<core-operators.adoc#_range_operator,range operator>>.
The following example iterates between today and the `LocalDate` six days from now,
printing out the day of the week for each iteration. As both range bounds are inclusive,
this prints all seven days of the week.
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=date_ranges,indent=0]
-------------------------------------
The `upto` method will accomplish the same as the range in the above example.
The `upto` method iterates from the earlier `start` value (inclusive) to the later `end` value
(also inclusive), calling the closure with the incremented `next` value once per iteration.
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=date_upto_date,indent=0]
-------------------------------------
The `downto` method iterates in the opposite direction, from a later `start` value
to an earlier `end` value.
The unit of iteration for `upto`, `downto`, and ranges is the same as the unit for addition
and subtraction: `LocalDate` iterates by one day at a time,
`YearMonth` iterates by one month, `Year` by one year, and everything else by one second.
Both methods also support an optional a `TemporalUnit` argument to change the unit of
iteration.
Consider the following example, where March 1st, 2018 is iterated up to March 2nd, 2018
using an iteration unit of
https://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoUnit.html#MONTHS[months].
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=date_upto_date_by_months,indent=0]
-------------------------------------
Since the `start` date is inclusive, the closure is called with a `next` date value of March 1st.
The `upto` method then increments the date by one month, yielding the date, April 1st.
Because this date is _after_ the specified `end` date of March 2nd, the iteration stops immediately,
having only called the closure once. This behavior is the same for the `downto` method except that
the iteration will stop as soon as the value of `next` becomes earlier than the targeted `end` date.
In short, when iterating with the `upto` or `downto` methods with a custom unit of iteration,
the current value of iteration will never exceed the end value.
=== Combining date/time values
The left-shift operator (`<<`) can be used to combine two JSR 310 types into an aggregate type.
For example, a `LocalDate` can be left-shifted into a `LocalTime` to produce a composite
`LocalDateTime` instance.
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=leftshift_operator,indent=0]
-------------------------------------
The left-shift operator is reflexive; the order of the operands does not matter.
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=leftshift_operator_reflexive,indent=0]
-------------------------------------
=== Creating periods and durations
The right-shift operator (`>>`) produces a value representing the period or duration between the
operands. For `ChronoLocalDate`, `YearMonth`, and `Year`, the operator yields
a `Period` instance:
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=rightshift_operator_period,indent=0]
-------------------------------------
The operator produces a `Duration` for the time-aware JSR types:
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=rightshift_operator_duration,indent=0]
-------------------------------------
If the value on the left-hand side of the operator is earlier than the value on the right-hand
side, the result is positive. If the left-hand side is later than the right-hand side, the
result is negative:
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=rightshift_operator_negative,indent=0]
-------------------------------------
== Converting between legacy and JSR 310 types
Despite the shortcomings of `Date`, `Calendar`, and `TimeZone` types in the `java.util` package
they are farily common in Java APIs (at least in those prior to Java 8).
To accommodate use of such APIs, Groovy provides methods for converting between the
JSR 310 types and legacy types.
Most JSR types have been fitted with `toDate()` and `toCalendar()` methods for
converting to relatively equivalent `java.util.Date` and `java.util.Calendar` values.
Both `ZoneId` and `ZoneOffset` have been given a `toTimeZone()` method for converting to
`java.util.TimeZone`.
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=todate_tocalendar,indent=0]
-------------------------------------
Note that when converting to a legacy type:
* Nanosecond values are truncated to milliseconds. A `LocalTime`, for example, with a `ChronoUnit.NANOS` value
of 999,999,999 nanoseconds translates to 999 milliseconds.
* When converting the "local" types (`LocalDate`, `LocalTime`, and `LocalDateTime`), the time zone of the
returned `Date` or `Calendar` will be the system default.
* When converting a time-only type (`LocalTime` or `OffsetTime`), the year/month/day of the `Date` or `Calendar` is set
to the current date.
* When converting a date-only type (`LocalDate`), the time value of the `Date` or `Calendar` will be cleared,
i.e. `00:00:00.000`.
* When converting an `OffsetDateTime` to a `Calendar`, only the hours and minutes of the `ZoneOffset` convey
into the corresponding `TimeZone`. Fortunately, Zone Offsets with non-zero seconds are rare.
Groovy has added a number of methods to `Date` and `Calendar`
for converting into the various JSR 310 types:
[source,groovy]
-------------------------------------
include::{projectdir}/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy[tags=to_jsr310_types,indent=0]
-------------------------------------