You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2018/03/21 16:44:55 UTC

[01/14] groovy git commit: add GDK Date/Time documentation(closes #674)

Repository: groovy
Updated Branches:
  refs/heads/GROOVY_2_6_X 235f9ccba -> 55865ea96


add GDK Date/Time documentation(closes #674)


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/963c90b4
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/963c90b4
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/963c90b4

Branch: refs/heads/GROOVY_2_6_X
Commit: 963c90b4fdb1f115791c12ee99f25fa281d57f54
Parents: b3bd886
Author: Joe Wolf <jo...@gmail.com>
Authored: Sat Mar 17 20:50:20 2018 -0400
Committer: paulk <pa...@asert.com.au>
Committed: Thu Mar 22 00:41:46 2018 +1000

----------------------------------------------------------------------
 src/spec/doc/core-gdk.adoc                      |   2 +
 src/spec/doc/working-with-datetime-types.adoc   | 341 +++++++++++++++++++
 .../gdk/WorkingWithDateTimeTypesTest.groovy     | 222 ++++++++++++
 3 files changed, 565 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/963c90b4/src/spec/doc/core-gdk.adoc
----------------------------------------------------------------------
diff --git a/src/spec/doc/core-gdk.adoc b/src/spec/doc/core-gdk.adoc
index 07caae9..2d239fe 100644
--- a/src/spec/doc/core-gdk.adoc
+++ b/src/spec/doc/core-gdk.adoc
@@ -25,6 +25,8 @@ include::{projectdir}/src/spec/doc/working-with-io.adoc[leveloffset=+1]
 
 include::{projectdir}/src/spec/doc/working-with-collections.adoc[leveloffset=+1]
 
+include::{projectdir}/src/spec/doc/working-with-datetime-types.adoc[leveloffset=+1]
+
 == Handy utilities
 
 === ConfigSlurper

http://git-wip-us.apache.org/repos/asf/groovy/blob/963c90b4/src/spec/doc/working-with-datetime-types.adoc
----------------------------------------------------------------------
diff --git a/src/spec/doc/working-with-datetime-types.adoc b/src/spec/doc/working-with-datetime-types.adoc
new file mode 100644
index 0000000..6567154
--- /dev/null
+++ b/src/spec/doc/working-with-datetime-types.adoc
@@ -0,0 +1,341 @@
+//////////////////////////////////////////
+
+  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
+:gdk: http://www.groovy-lang.org/gdk.html[Groovy development kit]
+:java-util-list: http://docs.oracle.com/javase/8/docs/api/java/util/List.html[java.util.List]
+:java-time-types: `java.time` types
+
+Groovy's syntax and extension methods within the {gdk} provide conveniences for using
+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 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 date 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 the value of `end` 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]
+-------------------------------------

http://git-wip-us.apache.org/repos/asf/groovy/blob/963c90b4/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy
----------------------------------------------------------------------
diff --git a/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy b/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy
new file mode 100644
index 0000000..99e7f8d
--- /dev/null
+++ b/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy
@@ -0,0 +1,222 @@
+package gdk
+
+import java.time.*
+import java.time.chrono.JapaneseDate
+import java.time.temporal.ChronoField
+import java.time.temporal.ChronoUnit
+
+class WorkingWithDateTimeTypesTest extends GroovyTestCase {
+
+    void testParsing() {
+        // tag::static_parsing[]
+        def date = LocalDate.parse('Jun 3, 04','MMM d, yy')
+        assert date == LocalDate.of(2004, Month.JUNE, 3)
+
+        def time = LocalTime.parse('4:45','H:mm')
+        assert time == LocalTime.of(4, 45, 0)
+
+        def offsetTime = OffsetTime.parse('09:47:51-1234', 'HH:mm:ssZ')
+        assert offsetTime == OffsetTime.of(9, 47, 51, 0, ZoneOffset.ofHoursMinutes(-12, -34))
+
+        def dateTime =  ZonedDateTime.parse('2017/07/11 9:47PM Pacific Standard Time', 'yyyy/MM/dd h:mma zzzz')
+        assert dateTime == ZonedDateTime.of(
+                               LocalDate.of(2017, 7, 11),
+                               LocalTime.of(21, 47, 0),
+                               ZoneId.of('America/Los_Angeles')
+                           )
+        // end::static_parsing[]
+    }
+
+    void testRange() {
+        // tag::date_ranges[]
+        def start = LocalDate.now()
+        def end = start + 6 // 6 days later
+        (start..end).each { date ->
+            println date.dayOfWeek
+        }
+        // end::date_ranges[]
+    }
+
+    void testUptoDownto() {
+        // tag::date_upto_date[]
+        def start = LocalDate.now()
+        def end = start + 6 // 6 days later
+        start.upto(end) { date ->
+            println date.dayOfWeek
+        }
+        // end::date_upto_date[]
+    }
+
+    void testUptoCustomUnit() {
+        // tag::date_upto_date_by_months[]
+        def start = LocalDate.of(2018, Month.MARCH, 2)
+        def end = start + 1 // 1 day later
+
+        int iterationCount = 0
+        start.upto(end, ChronoUnit.MONTHS) { date ->
+            println date
+            ++iterationCount
+        }
+
+        assert iterationCount == 1
+        // end::date_upto_date_by_months[]
+    }
+
+    void testPlusMinusWithTemporalAmounts() {
+        // tag::plus_minus_period[]
+        def aprilFools = LocalDate.of(2018, Month.APRIL, 1)
+
+        def nextAprilFools = aprilFools + Period.ofDays(365) // add 365 days
+        assert nextAprilFools.year == 2019
+
+        def idesOfMarch = aprilFools - Period.ofDays(17) // subtract 17 days
+        assert idesOfMarch.dayOfMonth == 15
+        assert idesOfMarch.month == Month.MARCH
+        // end::plus_minus_period[]
+    }
+
+    void testLocalDatePlusMinusInteger() {
+        def aprilFools = LocalDate.of(2018, Month.APRIL, 1)
+
+        // tag::localdate_plus_minus_integer[]
+        def nextAprilFools = aprilFools + 365 // add 365 days
+        def idesOfMarch = aprilFools - 17 // subtract 17 days
+        // end::localdate_plus_minus_integer[]
+
+        assert nextAprilFools.year == 2019
+        assert idesOfMarch.dayOfMonth == 15
+        assert idesOfMarch.month == Month.MARCH
+    }
+
+    void testLocalTimePlusMinusInteger() {
+        // tag::localtime_plus_minus_integer[]
+        def mars = LocalTime.of(12, 34, 56) // 12:34:56 pm
+
+        def thirtySecondsToMars = mars - 30 // go back 30 seconds
+        assert thirtySecondsToMars.second == 26
+        // end::localtime_plus_minus_integer[]
+    }
+
+    void testNextPrevious() {
+        // tag::next_previous[]
+        def year = Year.of(2000)
+        --year // decrement by one year
+        assert year.value == 1999
+
+        def offsetTime = OffsetTime.of(0, 0, 0, 0, ZoneOffset.UTC) // 00:00:00.000 UTC
+        offsetTime++ // increment by one second
+        assert offsetTime.second == 1
+        // end::next_previous[]
+    }
+
+    void testMultiplyDivide() {
+        // tag::multiply_divide[]
+        def period = Period.ofMonths(1) * 2 // a 1-month period times 2
+        assert period.months == 2
+
+        def duration = Duration.ofSeconds(10) / 5// a 10-second duration divided by 5
+        assert duration.seconds == 2
+        // end::multiply_divide[]
+    }
+
+    void testNegation() {
+        // tag::duration_negation[]
+        def duration = Duration.ofSeconds(-15)
+        def negated = -duration
+        assert negated.seconds == 15
+        // end::duration_negation[]
+    }
+
+    void testPropertyNotation() {
+        // tag::property_notation[]
+        def date = LocalDate.of(2018, Month.MARCH, 12)
+        assert date[ChronoField.YEAR] == 2018
+        assert date[ChronoField.MONTH_OF_YEAR] == Month.MARCH.value
+        assert date[ChronoField.DAY_OF_MONTH] == 12
+        assert date[ChronoField.DAY_OF_WEEK] == DayOfWeek.MONDAY.value
+
+        def period = Period.ofYears(2).withMonths(4).withDays(6)
+        assert period[ChronoUnit.YEARS] == 2
+        assert period[ChronoUnit.MONTHS] == 4
+        assert period[ChronoUnit.DAYS] == 6
+        // end::property_notation[]
+    }
+
+    void testLeftShift() {
+        // tag::leftshift_operator[]
+        MonthDay monthDay = Month.JUNE << 3 // June 3rd
+        LocalDate date = monthDay << Year.of(2015) // 3-Jun-2015
+        LocalDateTime dateTime = date << LocalTime.NOON // 3-Jun-2015 @ 12pm
+        OffsetDateTime offsetDateTime = dateTime << ZoneOffset.ofHours(-5) // 3-Jun-2015 @ 12pm UTC-5
+        // end::leftshift_operator[]
+        // tag::leftshift_operator_reflexive[]
+        def year = Year.of(2000)
+        def month = Month.DECEMBER
+
+        YearMonth a = year << month
+        YearMonth b = month << year
+        assert a == b
+        // end::leftshift_operator_reflexive[]
+    }
+
+    void testRightShift() {
+        // tag::rightshift_operator_period[]
+        def newYears = LocalDate.of(2018, Month.JANUARY, 1)
+        def aprilFools = LocalDate.of(2018, Month.APRIL, 1)
+
+        def period = newYears >> aprilFools
+        assert period instanceof Period
+        assert period.months == 3
+        // end::rightshift_operator_period[]
+
+        // tag::rightshift_operator_duration[]
+        def duration = LocalTime.NOON >> (LocalTime.NOON + 30)
+        assert duration instanceof Duration
+        assert duration.seconds == 30
+        // end::rightshift_operator_duration[]
+
+        // tag::rightshift_operator_negative[]
+        def decade = Year.of(2010) >> Year.of(2000)
+        assert decade.years == -10
+        // end::rightshift_operator_negative[]
+    }
+
+    void testToDateAndToCalendar() {
+        // tag::todate_tocalendar[]
+        // LocalDate to java.util.Date
+        def valentines = LocalDate.of(2018, Month.FEBRUARY, 14)
+        assert valentines.toDate().format('MMMM dd, yyyy') == 'February 14, 2018'
+
+        // LocalTime to java.util.Date
+        def noon = LocalTime.of(12, 0, 0)
+        assert noon.toDate().format('HH:mm:ss') == '12:00:00'
+
+        // ZoneId to java.util.TimeZone
+        def newYork = ZoneId.of('America/New_York')
+        assert newYork.toTimeZone() == TimeZone.getTimeZone('America/New_York')
+
+        // ZonedDateTime to java.util.Calendar
+        def valAtNoonInNY = ZonedDateTime.of(valentines, noon, newYork)
+        assert valAtNoonInNY.toCalendar().getTimeZone().toZoneId() == newYork
+        // end::todate_tocalendar[]
+    }
+
+    void testConvertToJSR310Types() {
+        // tag::to_jsr310_types[]
+        Date legacy = Date.parse('yyyy-MM-dd HH:mm:ss.SSS', '2010-04-03 10:30:58.999')
+
+        assert legacy.toLocalDate() == LocalDate.of(2010, 4, 3)
+        assert legacy.toLocalTime() == LocalTime.of(10, 30, 58, 999_000_000) // 999M ns = 999ms
+        assert legacy.toOffsetTime().hour == 10
+        assert legacy.toYear() == Year.of(2010)
+        assert legacy.toMonth() == Month.APRIL
+        assert legacy.toDayOfWeek() == DayOfWeek.SATURDAY
+        assert legacy.toMonthDay() == MonthDay.of(Month.APRIL, 3)
+        assert legacy.toYearMonth() == YearMonth.of(2010, Month.APRIL)
+        assert legacy.toLocalDateTime().year == 2010
+        assert legacy.toOffsetDateTime().dayOfMonth == 3
+        assert legacy.toZonedDateTime().zone == ZoneId.systemDefault()
+        // end::to_jsr310_types[]
+    }
+
+}


[09/14] groovy git commit: move datetime extensions to their own module

Posted by pa...@apache.org.
http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/subprojects/groovy-datetime/src/main/java/org/apache/groovy/datetime/extensions/DateTimeStaticExtensions.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-datetime/src/main/java/org/apache/groovy/datetime/extensions/DateTimeStaticExtensions.java b/subprojects/groovy-datetime/src/main/java/org/apache/groovy/datetime/extensions/DateTimeStaticExtensions.java
new file mode 100644
index 0000000..4b5865f
--- /dev/null
+++ b/subprojects/groovy-datetime/src/main/java/org/apache/groovy/datetime/extensions/DateTimeStaticExtensions.java
@@ -0,0 +1,250 @@
+/*
+ *  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.
+ */
+package org.apache.groovy.datetime.extensions;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Month;
+import java.time.MonthDay;
+import java.time.OffsetDateTime;
+import java.time.OffsetTime;
+import java.time.Period;
+import java.time.Year;
+import java.time.YearMonth;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * This class defines new static extension methods which appear on normal JDK
+ * Date/Time API (java.time) classes inside the Groovy environment.
+ */
+public class DateTimeStaticExtensions {
+
+    // Static methods only
+    private DateTimeStaticExtensions() {
+    }
+
+    /**
+     * Parse text into a {@link java.time.LocalDate} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return a LocalDate representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.LocalDate#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static LocalDate parse(final LocalDate type, CharSequence text, String pattern) {
+        return LocalDate.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Parse text into a {@link java.time.LocalDateTime} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return a LocalDateTime representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.LocalDateTime#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static LocalDateTime parse(final LocalDateTime type, CharSequence text, String pattern) {
+        return LocalDateTime.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Parse text into a {@link java.time.LocalTime} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return a LocalTime representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.LocalTime#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static LocalTime parse(final LocalTime type, CharSequence text, String pattern) {
+        return LocalTime.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Parse text into a {@link java.time.MonthDay} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return a MonthDay representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.MonthDay#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static MonthDay parse(final MonthDay type, CharSequence text, String pattern) {
+        return MonthDay.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Parse text into an {@link java.time.OffsetDateTime} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return an OffsetDateTime representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.OffsetDateTime#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static OffsetDateTime parse(final OffsetDateTime type, CharSequence text, String pattern) {
+        return OffsetDateTime.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Parse text into an {@link java.time.OffsetTime} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return an OffsetTime representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.OffsetTime#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static OffsetTime parse(final OffsetTime type, CharSequence text, String pattern) {
+        return OffsetTime.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Parse text into a {@link java.time.Year} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return a Year representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.Year#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static Year parse(final Year type, CharSequence text, String pattern) {
+        return Year.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Parse text into a {@link java.time.YearMonth} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return a YearMonth representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.YearMonth#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static YearMonth parse(final YearMonth type, CharSequence text, String pattern) {
+        return YearMonth.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Parse text into a {@link java.time.ZonedDateTime} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return a ZonedDateTime representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.ZonedDateTime#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static ZonedDateTime parse(final ZonedDateTime type, CharSequence text, String pattern) {
+        return ZonedDateTime.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Returns the {@link java.time.ZoneOffset} currently associated with the system default {@link java.time.ZoneId}.
+     *
+     * @param type placeholder variable used by Groovy categories; ignored for default static methods
+     * @return a ZoneOffset
+     * @see java.time.ZoneId#systemDefault()
+     * @since 3.0
+     */
+    public static ZoneOffset systemDefault(final ZoneOffset type) {
+        return DateTimeExtensions.getOffset(ZoneId.systemDefault());
+    }
+
+    /**
+     * Obtains a Period consisting of the number of years between two {@link java.time.Year} instances.
+     * The months and days of the Period will be zero.
+     * The result of this method can be a negative period if the end is before the start.
+     *
+     * @param type           placeholder variable used by Groovy categories; ignored for default static methods
+     * @param startInclusive the start {@link java.time.Year}, inclusive, not null
+     * @param endExclusive   the end {@link java.time.Year}, exclusive, not null
+     * @return a Period between the years
+     * @see java.time.Period#between(LocalDate, LocalDate)
+     */
+    public static Period between(final Period type, Year startInclusive, Year endExclusive) {
+        MonthDay now = MonthDay.of(Month.JANUARY, 1);
+        return Period.between(
+                DateTimeExtensions.leftShift(startInclusive, now),
+                DateTimeExtensions.leftShift(endExclusive, now))
+                .withDays(0)
+                .withMonths(0);
+    }
+
+    /**
+     * Obtains a Period consisting of the number of years and months between two {@link java.time.YearMonth} instances.
+     * The days of the Period will be zero.
+     * The result of this method can be a negative period if the end is before the start.
+     *
+     * @param type           placeholder variable used by Groovy categories; ignored for default static methods
+     * @param startInclusive the start {@link java.time.YearMonth}, inclusive, not null
+     * @param endExclusive   the end {@link java.time.YearMonth}, exclusive, not null
+     * @return a Period between the year/months
+     * @see java.time.Period#between(LocalDate, LocalDate)
+     */
+    public static Period between(final Period type, YearMonth startInclusive, YearMonth endExclusive) {
+        int dayOfMonth = 1;
+        return Period.between(
+                DateTimeExtensions.leftShift(startInclusive, dayOfMonth),
+                DateTimeExtensions.leftShift(endExclusive, dayOfMonth))
+                .withDays(0);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/subprojects/groovy-datetime/src/spec/doc/working-with-datetime-types.adoc
----------------------------------------------------------------------
diff --git a/subprojects/groovy-datetime/src/spec/doc/working-with-datetime-types.adoc b/subprojects/groovy-datetime/src/spec/doc/working-with-datetime-types.adoc
new file mode 100644
index 0000000..e4041c4
--- /dev/null
+++ b/subprojects/groovy-datetime/src/spec/doc/working-with-datetime-types.adoc
@@ -0,0 +1,338 @@
+//////////////////////////////////////////
+
+  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 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 date 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 the value of `end` 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]
+-------------------------------------

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/subprojects/groovy-datetime/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy
----------------------------------------------------------------------
diff --git a/subprojects/groovy-datetime/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy b/subprojects/groovy-datetime/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy
new file mode 100644
index 0000000..baed5fc
--- /dev/null
+++ b/subprojects/groovy-datetime/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy
@@ -0,0 +1,253 @@
+/*
+ *  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.
+ */
+package gdk
+
+import java.time.DayOfWeek
+import java.time.Duration
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.LocalTime
+import java.time.Month
+import java.time.MonthDay
+import java.time.OffsetDateTime
+import java.time.OffsetTime
+import java.time.Period
+import java.time.Year
+import java.time.YearMonth
+import java.time.ZoneId
+import java.time.ZoneOffset
+import java.time.ZonedDateTime
+import java.time.temporal.ChronoField
+import java.time.temporal.ChronoUnit
+
+class WorkingWithDateTimeTypesTest extends GroovyTestCase {
+
+    void testParsing() {
+        // tag::static_parsing[]
+        def date = LocalDate.parse('Jun 3, 04', 'MMM d, yy')
+        assert date == LocalDate.of(2004, Month.JUNE, 3)
+
+        def time = LocalTime.parse('4:45', 'H:mm')
+        assert time == LocalTime.of(4, 45, 0)
+
+        def offsetTime = OffsetTime.parse('09:47:51-1234', 'HH:mm:ssZ')
+        assert offsetTime == OffsetTime.of(9, 47, 51, 0, ZoneOffset.ofHoursMinutes(-12, -34))
+
+        def dateTime = ZonedDateTime.parse('2017/07/11 9:47PM Pacific Standard Time', 'yyyy/MM/dd h:mma zzzz')
+        assert dateTime == ZonedDateTime.of(
+                LocalDate.of(2017, 7, 11),
+                LocalTime.of(21, 47, 0),
+                ZoneId.of('America/Los_Angeles')
+        )
+        // end::static_parsing[]
+    }
+
+    void testRange() {
+        // tag::date_ranges[]
+        def start = LocalDate.now()
+        def end = start + 6 // 6 days later
+        (start..end).each { date ->
+            println date.dayOfWeek
+        }
+        // end::date_ranges[]
+    }
+
+    void testUptoDownto() {
+        // tag::date_upto_date[]
+        def start = LocalDate.now()
+        def end = start + 6 // 6 days later
+        start.upto(end) { date ->
+            println date.dayOfWeek
+        }
+        // end::date_upto_date[]
+    }
+
+    void testUptoCustomUnit() {
+        // tag::date_upto_date_by_months[]
+        def start = LocalDate.of(2018, Month.MARCH, 2)
+        def end = start + 1 // 1 day later
+
+        int iterationCount = 0
+        start.upto(end, ChronoUnit.MONTHS) { date ->
+            println date
+            ++iterationCount
+        }
+
+        assert iterationCount == 1
+        // end::date_upto_date_by_months[]
+    }
+
+    void testPlusMinusWithTemporalAmounts() {
+        // tag::plus_minus_period[]
+        def aprilFools = LocalDate.of(2018, Month.APRIL, 1)
+
+        def nextAprilFools = aprilFools + Period.ofDays(365) // add 365 days
+        assert nextAprilFools.year == 2019
+
+        def idesOfMarch = aprilFools - Period.ofDays(17) // subtract 17 days
+        assert idesOfMarch.dayOfMonth == 15
+        assert idesOfMarch.month == Month.MARCH
+        // end::plus_minus_period[]
+    }
+
+    void testLocalDatePlusMinusInteger() {
+        def aprilFools = LocalDate.of(2018, Month.APRIL, 1)
+
+        // tag::localdate_plus_minus_integer[]
+        def nextAprilFools = aprilFools + 365 // add 365 days
+        def idesOfMarch = aprilFools - 17 // subtract 17 days
+        // end::localdate_plus_minus_integer[]
+
+        assert nextAprilFools.year == 2019
+        assert idesOfMarch.dayOfMonth == 15
+        assert idesOfMarch.month == Month.MARCH
+    }
+
+    void testLocalTimePlusMinusInteger() {
+        // tag::localtime_plus_minus_integer[]
+        def mars = LocalTime.of(12, 34, 56) // 12:34:56 pm
+
+        def thirtySecondsToMars = mars - 30 // go back 30 seconds
+        assert thirtySecondsToMars.second == 26
+        // end::localtime_plus_minus_integer[]
+    }
+
+    void testNextPrevious() {
+        // tag::next_previous[]
+        def year = Year.of(2000)
+        --year // decrement by one year
+        assert year.value == 1999
+
+        def offsetTime = OffsetTime.of(0, 0, 0, 0, ZoneOffset.UTC) // 00:00:00.000 UTC
+        offsetTime++ // increment by one second
+        assert offsetTime.second == 1
+        // end::next_previous[]
+    }
+
+    void testMultiplyDivide() {
+        // tag::multiply_divide[]
+        def period = Period.ofMonths(1) * 2 // a 1-month period times 2
+        assert period.months == 2
+
+        def duration = Duration.ofSeconds(10) / 5// a 10-second duration divided by 5
+        assert duration.seconds == 2
+        // end::multiply_divide[]
+    }
+
+    void testNegation() {
+        // tag::duration_negation[]
+        def duration = Duration.ofSeconds(-15)
+        def negated = -duration
+        assert negated.seconds == 15
+        // end::duration_negation[]
+    }
+
+    void testPropertyNotation() {
+        // tag::property_notation[]
+        def date = LocalDate.of(2018, Month.MARCH, 12)
+        assert date[ChronoField.YEAR] == 2018
+        assert date[ChronoField.MONTH_OF_YEAR] == Month.MARCH.value
+        assert date[ChronoField.DAY_OF_MONTH] == 12
+        assert date[ChronoField.DAY_OF_WEEK] == DayOfWeek.MONDAY.value
+
+        def period = Period.ofYears(2).withMonths(4).withDays(6)
+        assert period[ChronoUnit.YEARS] == 2
+        assert period[ChronoUnit.MONTHS] == 4
+        assert period[ChronoUnit.DAYS] == 6
+        // end::property_notation[]
+    }
+
+    void testLeftShift() {
+        // tag::leftshift_operator[]
+        MonthDay monthDay = Month.JUNE << 3 // June 3rd
+        LocalDate date = monthDay << Year.of(2015) // 3-Jun-2015
+        LocalDateTime dateTime = date << LocalTime.NOON // 3-Jun-2015 @ 12pm
+        OffsetDateTime offsetDateTime = dateTime << ZoneOffset.ofHours(-5) // 3-Jun-2015 @ 12pm UTC-5
+        // end::leftshift_operator[]
+        // tag::leftshift_operator_reflexive[]
+        def year = Year.of(2000)
+        def month = Month.DECEMBER
+
+        YearMonth a = year << month
+        YearMonth b = month << year
+        assert a == b
+        // end::leftshift_operator_reflexive[]
+    }
+
+    void testRightShift() {
+        // tag::rightshift_operator_period[]
+        def newYears = LocalDate.of(2018, Month.JANUARY, 1)
+        def aprilFools = LocalDate.of(2018, Month.APRIL, 1)
+
+        def period = newYears >> aprilFools
+        assert period instanceof Period
+        assert period.months == 3
+        // end::rightshift_operator_period[]
+
+        // tag::rightshift_operator_duration[]
+        def duration = LocalTime.NOON >> (LocalTime.NOON + 30)
+        assert duration instanceof Duration
+        assert duration.seconds == 30
+        // end::rightshift_operator_duration[]
+
+        // tag::rightshift_operator_negative[]
+        def decade = Year.of(2010) >> Year.of(2000)
+        assert decade.years == -10
+        // end::rightshift_operator_negative[]
+    }
+
+    void testToDateAndToCalendar() {
+        // tag::todate_tocalendar[]
+        // LocalDate to java.util.Date
+        def valentines = LocalDate.of(2018, Month.FEBRUARY, 14)
+        assert valentines.toDate().format('MMMM dd, yyyy') == 'February 14, 2018'
+
+        // LocalTime to java.util.Date
+        def noon = LocalTime.of(12, 0, 0)
+        assert noon.toDate().format('HH:mm:ss') == '12:00:00'
+
+        // ZoneId to java.util.TimeZone
+        def newYork = ZoneId.of('America/New_York')
+        assert newYork.toTimeZone() == TimeZone.getTimeZone('America/New_York')
+
+        // ZonedDateTime to java.util.Calendar
+        def valAtNoonInNY = ZonedDateTime.of(valentines, noon, newYork)
+        assert valAtNoonInNY.toCalendar().getTimeZone().toZoneId() == newYork
+        // end::todate_tocalendar[]
+    }
+
+    void testConvertToJSR310Types() {
+        // tag::to_jsr310_types[]
+        Date legacy = Date.parse('yyyy-MM-dd HH:mm:ss.SSS', '2010-04-03 10:30:58.999')
+
+        assert legacy.toLocalDate() == LocalDate.of(2010, 4, 3)
+        assert legacy.toLocalTime() == LocalTime.of(10, 30, 58, 999_000_000) // 999M ns = 999ms
+        assert legacy.toOffsetTime().hour == 10
+        assert legacy.toYear() == Year.of(2010)
+        assert legacy.toMonth() == Month.APRIL
+        assert legacy.toDayOfWeek() == DayOfWeek.SATURDAY
+        assert legacy.toMonthDay() == MonthDay.of(Month.APRIL, 3)
+        assert legacy.toYearMonth() == YearMonth.of(2010, Month.APRIL)
+        assert legacy.toLocalDateTime().year == 2010
+        assert legacy.toOffsetDateTime().dayOfMonth == 3
+        assert legacy.toZonedDateTime().zone == ZoneId.systemDefault()
+        // end::to_jsr310_types[]
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/subprojects/groovy-datetime/src/test/java/groovy/DateTimeTest.groovy
----------------------------------------------------------------------
diff --git a/subprojects/groovy-datetime/src/test/java/groovy/DateTimeTest.groovy b/subprojects/groovy-datetime/src/test/java/groovy/DateTimeTest.groovy
new file mode 100644
index 0000000..bda4a4b
--- /dev/null
+++ b/subprojects/groovy-datetime/src/test/java/groovy/DateTimeTest.groovy
@@ -0,0 +1,851 @@
+/*
+ *  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.
+ */
+package groovy
+
+import java.text.SimpleDateFormat
+import java.time.Duration
+import java.time.Instant
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.LocalTime
+import java.time.MonthDay
+import java.time.OffsetDateTime
+import java.time.OffsetTime
+import java.time.Period
+import java.time.YearMonth
+import java.time.ZoneId
+import java.time.ZoneOffset
+import java.time.ZonedDateTime
+import java.time.chrono.JapaneseDate
+import java.time.temporal.ChronoField
+import java.time.temporal.ChronoUnit
+
+class DateTimeTest extends GroovyTestCase {
+
+    void testDurationPlusMinusPositiveNegative() {
+        def duration = Duration.ofSeconds(10)
+        def longer = duration + 5
+        def shorter = duration - 5
+
+        assert longer.seconds == 15
+        assert shorter.seconds == 5
+        assert (++longer).seconds == 16
+        assert (--shorter).seconds == 4
+    }
+
+    void testInstantPlusMinusPositiveNegative() {
+        def epoch = Instant.ofEpochMilli(0)
+
+        def twoSecPastEpoch = epoch + 2
+        def oneSecPastEpoch = twoSecPastEpoch - 1
+
+        assert oneSecPastEpoch.epochSecond == 1
+        assert twoSecPastEpoch.epochSecond == 2
+        assert (++twoSecPastEpoch).epochSecond == 3
+        assert (--oneSecPastEpoch).epochSecond == 0
+    }
+
+    void testLocalDatePlusMinusPositiveNegative() {
+        def epoch = LocalDate.of(1970, Month.JANUARY, 1)
+
+        def twoDaysPastEpoch = epoch + 2
+        def oneDayPastEpoch = twoDaysPastEpoch - 1
+
+        assert oneDayPastEpoch.dayOfMonth == 2
+        assert twoDaysPastEpoch.dayOfMonth == 3
+        assert (++twoDaysPastEpoch).dayOfMonth == 4
+        assert (--oneDayPastEpoch).dayOfMonth == 1
+    }
+
+    void testLocalDateTimePlusMinusPositiveNegative() {
+        def epoch = LocalDateTime.of(1970, Month.JANUARY, 1, 0, 0, 0, 0)
+
+        def twoSecsPastEpoch = epoch + 2
+        def oneSecPastEpoch = twoSecsPastEpoch - 1
+
+        assert oneSecPastEpoch.second == 1
+        assert twoSecsPastEpoch.second == 2
+        assert (++twoSecsPastEpoch).second == 3
+        assert (--oneSecPastEpoch).second == 0
+    }
+
+    void testLocalTimePlusMinusPositiveNegative() {
+        def epoch = LocalTime.of(0, 0, 0, 0)
+
+        def twoSecsPastEpoch = epoch + 2
+        def oneSecPastEpoch = twoSecsPastEpoch - 1
+
+        assert oneSecPastEpoch.second == 1
+        assert twoSecsPastEpoch.second == 2
+        assert (++twoSecsPastEpoch).second == 3
+        assert (--oneSecPastEpoch).second == 0
+    }
+
+    void testOffsetDateTimePlusMinusPositiveNegative() {
+        def epoch = OffsetDateTime.of(LocalDateTime.of(1970, Month.JANUARY, 1, 0, 0, 0, 0),
+                ZoneOffset.ofHours(0))
+
+        def twoSecsPastEpoch = epoch + 2
+        def oneSecPastEpoch = twoSecsPastEpoch - 1
+
+        assert oneSecPastEpoch.second == 1
+        assert twoSecsPastEpoch.second == 2
+        assert (++twoSecsPastEpoch).second == 3
+        assert (--oneSecPastEpoch).second == 0
+    }
+
+    void testOffsetTimePlusMinusPositiveNegative() {
+        def epoch = OffsetTime.of(LocalTime.of(0, 0, 0, 0),
+                ZoneOffset.ofHours(0))
+
+        def twoSecsPastEpoch = epoch + 2
+        def oneSecPastEpoch = twoSecsPastEpoch - 1
+
+        assert oneSecPastEpoch.second == 1
+        assert twoSecsPastEpoch.second == 2
+        assert (++twoSecsPastEpoch).second == 3
+        assert (--oneSecPastEpoch).second == 0
+    }
+
+    void testPeriodPlusMinusPositiveNegative() {
+        def fortnight = Period.ofDays(14)
+
+        def fortnightAndTwoDays = fortnight + 2
+        def fortnightAndOneDay = fortnightAndTwoDays - 1
+
+        assert fortnightAndOneDay.days == 15
+        assert fortnightAndTwoDays.days == 16
+        assert (++fortnightAndTwoDays).days == 17
+        assert (--fortnightAndOneDay).days == 14
+    }
+
+    void testYearPlusMinusPositiveNegative() {
+        def epoch = Year.of(1970)
+
+        def twoYearsAfterEpoch = epoch + 2
+        def oneYearAfterEpoch = twoYearsAfterEpoch - 1
+
+        assert oneYearAfterEpoch.value == 1971
+        assert twoYearsAfterEpoch.value == 1972
+        assert (++twoYearsAfterEpoch).value == 1973
+        assert (--oneYearAfterEpoch).value == 1970
+    }
+
+    void testYearMonthPlusMinusPositiveNegative() {
+        def epoch = YearMonth.of(1970, Month.JANUARY)
+
+        def twoMonthsAfterEpoch = epoch + 2
+        def oneMonthAfterEpoch = twoMonthsAfterEpoch - 1
+
+        assert oneMonthAfterEpoch.month == Month.FEBRUARY
+        assert twoMonthsAfterEpoch.month == Month.MARCH
+        assert (++twoMonthsAfterEpoch).month == Month.APRIL
+        assert (--oneMonthAfterEpoch).month == Month.JANUARY
+    }
+
+    void testZonedDateTimePlusMinusPositiveNegative() {
+        def epoch = ZonedDateTime.of(LocalDateTime.of(1970, Month.JANUARY, 1, 0, 0, 0, 0),
+                ZoneId.systemDefault())
+
+        def twoSecsPastEpoch = epoch + 2
+        def oneSecPastEpoch = twoSecsPastEpoch - 1
+
+        assert oneSecPastEpoch.second == 1
+        assert twoSecsPastEpoch.second == 2
+        assert (++twoSecsPastEpoch).second == 3
+        assert (--oneSecPastEpoch).second == 0
+    }
+
+    void testDayOfWeekPlusMinus() {
+        def mon = DayOfWeek.MONDAY
+
+        assert mon + 4 == DayOfWeek.FRIDAY
+        assert mon - 4 == DayOfWeek.THURSDAY
+    }
+
+    void testMonthPlusMinus() {
+        def jan = Month.JANUARY
+
+        assert jan + 4 == Month.MAY
+        assert jan - 4 == Month.SEPTEMBER
+    }
+
+    void testDurationPositiveNegative() {
+        def positiveDuration = Duration.ofSeconds(3)
+        assert (-positiveDuration).seconds == -3
+
+        def negativeDuration = Duration.ofSeconds(-5)
+        assert (+negativeDuration).seconds == 5
+    }
+
+    void testDurationMultiplyDivide() {
+        def duration = Duration.ofSeconds(60)
+
+        assert (duration / 2).seconds == 30
+        assert (duration * 2).seconds == 120
+    }
+
+    void testDurationIsPositiveIsNonnegativeIsNonpositive() {
+        def pos = Duration.ofSeconds(10)
+        assert pos.isPositive() == true
+        assert pos.isNonpositive() == false
+        assert pos.isNonnegative() == true
+
+        def neg = Duration.ofSeconds(-10)
+        assert neg.isPositive() == false
+        assert neg.isNonpositive() == true
+        assert neg.isNonnegative() == false
+
+        assert Duration.ZERO.isPositive() == false
+        assert Duration.ZERO.isNonpositive() == true
+        assert Duration.ZERO.isNonnegative() == true
+    }
+
+    void testPeriodPositiveNegative() {
+        def positivePeriod = Period.of(1,2,3)
+        Period madeNegative = -positivePeriod
+        assert madeNegative.years == -1 : "All Period fields should be made negative"
+        assert madeNegative.months == -2
+        assert madeNegative.days == -3
+
+        def negativePeriod = Period.of(-1,2,-3)
+        Period madePositive = +negativePeriod
+        assert madePositive.years == 1 : "Negative Period fields should be made positive"
+        assert madePositive.months == 2 : "Positive Period fields should remain positive"
+        assert madePositive.days == 3
+    }
+
+    void testPeriodMultiply() {
+        def period = Period.of(1,1,1)
+        Period doublePeriod = period * 2
+        assert doublePeriod.years == 2
+        assert doublePeriod.months == 2
+        assert doublePeriod.days == 2
+    }
+
+    void testPeriodIsPositiveIsNonnegativeIsNonpositive() {
+        def pos = Period.ofDays(10)
+        assert pos.isPositive() == true
+        assert pos.isNonpositive() == false
+        assert pos.isNonnegative() == true
+
+        def neg = Period.ofDays(-10)
+        assert neg.isPositive() == false
+        assert neg.isNonpositive() == true
+        assert neg.isNonnegative() == false
+
+        assert Period.ZERO.isPositive() == false
+        assert Period.ZERO.isNonpositive() == true
+        assert Period.ZERO.isNonnegative() == true
+    }
+
+    void testTemporalGetAt() {
+        def epoch = Instant.ofEpochMilli(0)
+        assert epoch[ChronoField.INSTANT_SECONDS] == 0
+    }
+
+    void testTemporalAmountGetAt() {
+        def duration = Duration.ofHours(10)
+        assert duration[ChronoUnit.SECONDS] == 36_000
+    }
+
+    void testZoneOffsetGetAt() {
+        def offset = ZoneOffset.ofTotalSeconds(360)
+        assert offset[ChronoField.OFFSET_SECONDS] == 360
+    }
+
+    void testTemporalRightShift() {
+        def epoch = Instant.ofEpochMilli(0)
+        def dayAfterEpoch = epoch + (60 * 60 * 24)
+        Duration instantDuration = epoch >> dayAfterEpoch
+        assert instantDuration == Duration.ofDays(1)
+    }
+
+    void testLocalDateRightShift() {
+        def localDate1 = LocalDate.of(2000, Month.JANUARY, 1)
+        def localDate2 = localDate1.plusYears(2)
+        Period localDatePeriod = localDate1 >> localDate2
+        assert localDatePeriod.years == 2
+    }
+
+    void testYearRightShift() {
+        def year1 = Year.of(2000)
+        def year2 = Year.of(2018)
+        Period yearPeriod = year1 >> year2
+        assert yearPeriod.years == 18
+    }
+
+    void testYearMonthRightShift() {
+        def yearMonth1 = YearMonth.of(2018, Month.JANUARY)
+        def yearMonth2 = YearMonth.of(2018, Month.MARCH)
+        Period yearMonthPeriod = yearMonth1 >> yearMonth2
+        assert yearMonthPeriod.months == 2
+    }
+
+    void testRightShiftDifferentTypes() {
+        try {
+            LocalDate.now() >> LocalTime.now()
+            fail('Should not be able to use right shift on different Temporal types.')
+        } catch (e) {
+            assert e instanceof GroovyRuntimeException
+        }
+    }
+
+    void testUptoDifferentTypes() {
+        try {
+            LocalDate.now().upto(JapaneseDate.now().plus(1, ChronoUnit.MONTHS)) { d -> }
+            fail('Cannot use upto() with two different Temporal types.')
+        } catch (e) {
+            assert e instanceof GroovyRuntimeException
+        }
+    }
+
+    void testDowntoDifferentTypes() {
+        try {
+            LocalDate.now().downto(JapaneseDate.now().minus(1, ChronoUnit.MONTHS)) { d -> }
+            fail('Cannot use downto() with two different argument types.')
+        } catch (e) {
+            assert e instanceof GroovyRuntimeException
+        }
+    }
+
+    void testUptoSelfWithDefaultUnit() {
+        def epoch = Instant.ofEpochMilli(0)
+
+        int iterations = 0
+        epoch.upto(epoch) {
+            ++iterations
+            assert it == epoch: 'upto closure should be provided with arg'
+        }
+        assert iterations == 1: 'Iterating upto same value should call closure once'
+    }
+
+    void testDowntoSelfWithDefaultUnit() {
+        def epoch = Instant.ofEpochMilli(0)
+        int iterations = 0
+        epoch.downto(epoch) {
+            ++iterations
+            assert it == epoch: 'downto closure should be provided with arg'
+        }
+        assert iterations == 1: 'Iterating downto same value should call closure once'
+    }
+
+    void testUptoWithSecondsDefaultUnit() {
+        def epoch = Instant.ofEpochMilli(0)
+
+        int iterations = 0
+        Instant end = null
+        epoch.upto(epoch + 1) {
+            ++iterations
+            end = it
+        }
+        assert iterations == 2: 'Iterating upto Temporal+1 value should call closure twice'
+        assert end.epochSecond == 1: 'Unexpected upto final value'
+    }
+
+    void testDowntoWithSecondsDefaultUnit() {
+        def epoch = Instant.ofEpochMilli(0)
+
+        int iterations = 0
+        Instant end = null
+        epoch.downto(epoch - 1) {
+            ++iterations
+            end = it
+        }
+        assert iterations == 2 : 'Iterating downto Temporal+1 value should call closure twice'
+        assert end.epochSecond == -1 : 'Unexpected downto final value'
+    }
+
+    void testUptoWithYearsDefaultUnit() {
+        def endYear = null
+        Year.of(1970).upto(Year.of(1971)) { year -> endYear = year }
+        assert endYear.value == 1971
+    }
+
+    void testDowntoWithYearsDefaultUnit() {
+        def endYear = null
+        Year.of(1971).downto(Year.of(1970)) { year -> endYear = year }
+        assert endYear.value == 1970
+    }
+
+    void testUptoWithMonthsDefaultUnit() {
+        def endYearMonth = null
+        YearMonth.of(1970, Month.JANUARY).upto(YearMonth.of(1970, Month.FEBRUARY)) { yearMonth ->
+            endYearMonth = yearMonth
+        }
+        assert endYearMonth.month == Month.FEBRUARY
+    }
+
+    void testDowntoWithMonthsDefaultUnit() {
+        def endYearMonth = null
+        YearMonth.of(1970, Month.FEBRUARY).downto(YearMonth.of(1970, Month.JANUARY)) { yearMonth ->
+            endYearMonth = yearMonth
+        }
+        assert endYearMonth.month == Month.JANUARY
+    }
+
+    void testUptoWithDaysDefaultUnit() {
+        def endLocalDate = null
+        LocalDate.of(1970, Month.JANUARY, 1).upto(LocalDate.of(1970, Month.JANUARY, 2)) {  localDate ->
+            endLocalDate = localDate
+        }
+        assert endLocalDate.dayOfMonth == 2
+    }
+
+    void testDowntoWithDaysDefaultUnit() {
+        def endLocalDate = null
+        LocalDate.of(1970, Month.JANUARY, 2).downto(LocalDate.of(1970, Month.JANUARY, 1)) {  localDate ->
+            endLocalDate = localDate
+        }
+        assert endLocalDate.dayOfMonth == 1
+    }
+
+    void testUptoWithIllegalReversedArguments() {
+        def epoch = Instant.ofEpochMilli(0)
+        try {
+            epoch.upto(epoch - 1) {
+                fail('upto() should fail when passed earlier arg')
+            }
+        } catch (GroovyRuntimeException e) {
+        }
+    }
+
+    void testDowntoWithIllegalReversedArguments() {
+        def epoch = Instant.ofEpochMilli(0)
+        try {
+            epoch.downto(epoch + 1) {
+                fail('downto() should fail when passed earlier arg')
+            }
+        } catch (GroovyRuntimeException e) {}
+    }
+
+    void testUptoSelfWithCustomUnit() {
+        def today = LocalDate.now()
+
+        int iterations = 0
+        today.upto(today, ChronoUnit.MONTHS) {
+            ++iterations
+            assert it == today: 'upto closure should be provided with arg'
+        }
+        assert iterations == 1: 'Iterating upto same value should call closure once'
+    }
+
+    void testDowntoSelfWithCustomUnit() {
+        def today = LocalDate.now()
+
+        int iterations = 0
+        today.downto(today, ChronoUnit.MONTHS) {
+            ++iterations
+            assert it == today: 'downto closure should be provided with arg'
+        }
+        assert iterations == 1: 'Iterating downto same value should call closure once'
+    }
+
+    void testUptoWithCustomUnit() {
+        LocalDateTime from = LocalDateTime.of(2018, Month.FEBRUARY, 11, 22, 9, 34)
+        // one second beyond one iteration
+        LocalDateTime to = from.plusDays(1).plusSeconds(1)
+
+        int iterations = 0
+        LocalDateTime end = null
+        from.upto(to, ChronoUnit.DAYS) {
+            ++iterations
+            end = it
+        }
+        assert iterations == 2
+        assert end.dayOfMonth == 12: "Upto should have iterated by DAYS twice"
+    }
+
+    void testDowntoWithCustomUnit() {
+        LocalDateTime from = LocalDateTime.of(2018, Month.FEBRUARY, 11, 22, 9, 34)
+        // one day beyond one iteration
+        LocalDateTime to = from.minusYears(1).minusDays(1)
+
+        int iterations = 0
+        LocalDateTime end = null
+        from.downto(to, ChronoUnit.YEARS) {
+            ++iterations
+            end = it
+        }
+        assert iterations == 2
+        assert end.year == 2017 : "Downto should have iterated by YEARS twice"
+    }
+
+    void testInstantToDateToCalendar() {
+        def epoch = Instant.ofEpochMilli(0).plusNanos(999_999)
+
+        def date = epoch.toDate()
+        def cal = epoch.toCalendar()
+        assert cal.time == date
+        def sdf = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss.SSS')
+        sdf.timeZone = TimeZone.getTimeZone('GMT')
+        assert sdf.format(date) == '1970-01-01 00:00:00.000'
+    }
+
+    void testLocalDateToDateToCalendar() {
+        def ld = LocalDate.of(2018, Month.FEBRUARY, 12)
+
+        Calendar cal = ld.toCalendar()
+        assert cal.get(Calendar.YEAR) == 2018
+        assert cal.get(Calendar.MONTH) == Calendar.FEBRUARY
+        assert cal.get(Calendar.DAY_OF_MONTH) == 12
+        assert cal.timeZone.getID() == TimeZone.default.getID()
+
+        Date date = ld.toDate()
+        assert date.format('yyyy-MM-dd') == '2018-02-12'
+    }
+
+    void testLocalDateTimeToDateToCalendar() {
+        def ldt = LocalDateTime.of(2018, Month.FEBRUARY, 12, 22, 26, 30, 123_999_999)
+
+        Calendar cal = ldt.toCalendar()
+        assert cal.get(Calendar.YEAR) == 2018
+        assert cal.get(Calendar.MONTH) == Calendar.FEBRUARY
+        assert cal.get(Calendar.DAY_OF_MONTH) == 12
+        assert cal.get(Calendar.HOUR_OF_DAY) == 22
+        assert cal.get(Calendar.MINUTE) == 26
+        assert cal.get(Calendar.SECOND) == 30
+        assert cal.get(Calendar.MILLISECOND) == 123
+        assert cal.timeZone.getID() == TimeZone.default.getID()
+
+        Date date = ldt.toDate()
+        assert date.format('yyyy-MM-dd HH:mm:ss.SSS') == '2018-02-12 22:26:30.123'
+    }
+
+    void testLocalTimeToDateToCalendar() {
+        def today = Calendar.instance
+        def lt = LocalTime.of(22, 38, 20, 9_999_999)
+
+        Calendar cal = lt.toCalendar()
+        assert cal.get(Calendar.YEAR) == today.get(Calendar.YEAR) : 'LocalTime.toCalendar() should have current year'
+        assert cal.get(Calendar.MONTH) == today.get(Calendar.MONTH) : 'LocalTime.toCalendar() should have current month'
+        assert cal.get(Calendar.DAY_OF_MONTH) == today.get(Calendar.DAY_OF_MONTH) : 'LocalTime.toCalendar() should have current day'
+        assert cal.get(Calendar.HOUR_OF_DAY) == 22
+        assert cal.get(Calendar.MINUTE) == 38
+        assert cal.get(Calendar.SECOND) == 20
+        assert cal.get(Calendar.MILLISECOND) == 9
+        assert cal.timeZone.getID() == TimeZone.default.getID()
+
+        Date date = lt.toDate()
+        assert date.format('HH:mm:ss.SSS') == '22:38:20.009'
+    }
+
+    void testOffsetDateTimeToDateToCalendar() {
+        def ld = LocalDate.of(2018, Month.FEBRUARY, 12)
+        def lt = LocalTime.of(22, 46, 10, 16_000_001)
+        def offset = ZoneOffset.ofHours(-5)
+        def odt = OffsetDateTime.of(ld, lt, offset)
+
+        Calendar cal = odt.toCalendar()
+        assert cal.get(Calendar.YEAR) == 2018
+        assert cal.get(Calendar.MONTH) == Calendar.FEBRUARY
+        assert cal.get(Calendar.DAY_OF_MONTH) == 12
+        assert cal.get(Calendar.HOUR_OF_DAY) == 22
+        assert cal.get(Calendar.MINUTE) == 46
+        assert cal.get(Calendar.SECOND) == 10
+        assert cal.get(Calendar.MILLISECOND) == 16
+        assert cal.timeZone.getOffset(System.currentTimeMillis()) == -5 * 60 * 60 * 1000
+
+        Date date = odt.toDate()
+        def sdf = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss.SSS Z')
+        sdf.timeZone = cal.timeZone
+        assert sdf.format(date) == '2018-02-12 22:46:10.016 -0500'
+    }
+
+    void testOffsetTimeToDateToCalendar() {
+        def lt = LocalTime.of(22, 53, 2, 909_900_009)
+        def offset = ZoneOffset.ofHours(-4)
+        def ot = OffsetTime.of(lt, offset)
+        Calendar today = Calendar.getInstance(TimeZone.getTimeZone('GMT-4'))
+
+        Calendar cal = ot.toCalendar()
+        assert cal.get(Calendar.YEAR) == today.get(Calendar.YEAR) : 'OffsetTime.toCalendar() should have current year'
+        assert cal.get(Calendar.MONTH) == today.get(Calendar.MONTH) : 'OffsetTime.toCalendar() should have current month'
+        assert cal.get(Calendar.DAY_OF_MONTH) == today.get(Calendar.DAY_OF_MONTH) : 'OffsetTime.toCalendar() should have current day'
+        assert cal.get(Calendar.HOUR_OF_DAY) == 22
+        assert cal.get(Calendar.MINUTE) == 53
+        assert cal.get(Calendar.SECOND) == 2
+        assert cal.get(Calendar.MILLISECOND) == 909
+        assert cal.timeZone.getOffset(System.currentTimeMillis()) == -4 * 60 * 60 * 1000
+
+        Date date = ot.toDate()
+        def sdf = new SimpleDateFormat('HH:mm:ss.SSS Z')
+        sdf.timeZone = cal.timeZone
+        assert sdf.format(date) == '22:53:02.909 -0400'
+    }
+
+    void testZonedDateTimeToDateToCalendar() {
+        def ldt = LocalDateTime.of(2018, Month.FEBRUARY, 13, 20, 33, 57)
+        def zoneId = ZoneId.ofOffset('GMT', ZoneOffset.ofHours(3))
+        def zdt = ZonedDateTime.of(ldt, zoneId)
+
+        Calendar cal = zdt.toCalendar()
+        assert cal.get(Calendar.YEAR) == 2018
+        assert cal.get(Calendar.MONTH) == Calendar.FEBRUARY
+        assert cal.get(Calendar.DAY_OF_MONTH) == 13
+        assert cal.get(Calendar.HOUR_OF_DAY) == 20
+        assert cal.get(Calendar.MINUTE) == 33
+        assert cal.get(Calendar.SECOND) == 57
+        assert cal.get(Calendar.MILLISECOND) == 0
+        assert cal.timeZone.getOffset(System.currentTimeMillis()) == 3 * 60 * 60 * 1000
+
+        Date date = zdt.toDate()
+        def sdf = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss.SSS Z')
+        sdf.timeZone = cal.timeZone
+        assert sdf.format(date) == '2018-02-13 20:33:57.000 +0300'
+    }
+
+    void testZoneOffsetExtensionProperties() {
+        def offset = ZoneOffset.ofHoursMinutesSeconds(3,4,5)
+        assert offset.hours == 3
+        assert offset.minutes == 4
+        assert offset.seconds == 5
+
+        def negOffset = ZoneOffset.ofHoursMinutesSeconds(-1, -2, -3)
+        assert negOffset.hours == -1
+        assert negOffset.minutes == -2
+        assert negOffset.seconds == -3
+    }
+
+    void testZoneOffsetToZimeZone() {
+        TimeZone utcTz = ZoneOffset.UTC.toTimeZone()
+        assert utcTz.getID() == 'GMT'
+
+        TimeZone noSecsTz = ZoneOffset.ofHoursMinutes(1, 30).toTimeZone()
+        assert noSecsTz.getID() == 'GMT+01:30'
+
+        TimeZone secsTz = ZoneOffset.ofHoursMinutesSeconds(-4, -15, -30).toTimeZone()
+        assert secsTz.getID() == 'GMT-04:15'
+    }
+
+    void testZoneIdExtensionProperties() {
+        def offset = ZoneOffset.ofHours(7)
+        def zoneId = ZoneId.ofOffset('GMT', offset)
+
+        assert zoneId.offset.totalSeconds == offset.totalSeconds
+        assert zoneId.getOffset(Instant.now()).totalSeconds == offset.totalSeconds
+        assert zoneId.shortName == 'GMT+07:00'
+        assert zoneId.fullName == 'GMT+07:00'
+
+        ZoneId ny = ZoneId.of('America/New_York')
+        assert ny.getShortName(Locale.US) == 'ET'
+        assert ny.getFullName(Locale.US) == 'Eastern Time'
+    }
+
+    void testZoneIdToTimeZone() {
+        ZoneId ny = ZoneId.of('America/New_York')
+
+        assert ny.toTimeZone() == TimeZone.getTimeZone(ny)
+    }
+
+    void testYearExtensionProperties() {
+        def year = Year.of(2009)
+        assert year.era == 1
+        assert year.yearOfEra == 2009
+    }
+
+    void testDayOfWeekExtensionProperties() {
+        assert DayOfWeek.SUNDAY.weekend
+        assert DayOfWeek.MONDAY.weekday
+    }
+
+    void testYear_Month_leftShift() {
+        def a = Year.now()
+        def b = Month.JULY
+
+        YearMonth x = a << b
+        YearMonth y = b << a
+        assert x == y
+    }
+
+    void testYear_MonthDay_leftShift() {
+        def a = Year.now()
+        def b = MonthDay.now()
+
+        LocalDate x = a << b
+        LocalDate y = b << a
+        assert x == y
+    }
+
+    void testMonthDay_leftShift() {
+        LocalDate d = MonthDay.of(Month.FEBRUARY, 13) << 2018
+        assert d.year == 2018
+        assert d.month == Month.FEBRUARY
+        assert d.dayOfMonth == 13
+    }
+
+    void testMonth_leftShift() {
+        MonthDay md = Month.JANUARY << 10
+        assert md.month == Month.JANUARY
+        assert md.dayOfMonth == 10
+    }
+
+    void testLocalDate_LocalTime_leftShift() {
+        def a = LocalDate.now()
+        def b = LocalTime.now()
+
+        LocalDateTime x = a << b
+        LocalDateTime y = b << a
+        assert x == y
+    }
+
+    void testLocalDate_OffsetTime_leftShift() {
+        def a = LocalDate.now()
+        def b = OffsetTime.now()
+
+        OffsetDateTime x = a << b
+        OffsetDateTime y = b << a
+        assert x == y
+    }
+
+    void testLocalDateTime_ZoneOffset_leftShift() {
+        def a = LocalDateTime.now()
+        def b = ZoneOffset.ofHours(5)
+
+        OffsetDateTime x = a << b
+        OffsetDateTime y = b << a
+        assert x == y
+    }
+
+    void testLocalDateTime_ZoneId_leftShift() {
+        def a = LocalDateTime.now()
+        def b = ZoneId.systemDefault()
+
+        ZonedDateTime x = a << b
+        ZonedDateTime y = b << a
+        assert x == y
+    }
+
+    void testLocalTime_ZoneOffset_leftShift() {
+        def a = LocalTime.now()
+        def b = ZoneOffset.ofHours(5)
+
+        OffsetTime x = a << b
+        OffsetTime y = b << a
+        assert x == y
+    }
+
+    void testLocalDateTimeClearTime() {
+        def d = LocalDateTime.of(LocalDate.now(), LocalTime.of(8, 9, 10, 100_032))
+        d = d.clearTime()
+
+        assert d.hour == 0
+        assert d.minute == 0
+        assert d.second == 0
+        assert d.nano == 0
+    }
+
+    void testOffsetDateTimeClearTime() {
+        def offset = ZoneOffset.ofHours(-1)
+        def d = OffsetDateTime.of(LocalDate.now(), LocalTime.of(8, 9, 10, 100_032), offset)
+        d = d.clearTime()
+
+        assert d.hour == 0
+        assert d.minute == 0
+        assert d.second == 0
+        assert d.nano == 0
+        assert d.offset == offset : 'cleartTime() should not change offset'
+    }
+
+    void testZonedDateTimeClearTime() {
+        def zone =  ZoneId.of('America/New_York')
+        def d = ZonedDateTime.of(LocalDate.now(), LocalTime.of(8, 9, 10, 100_032), zone)
+        d = d.clearTime()
+
+        assert d.hour == 0
+        assert d.minute == 0
+        assert d.second == 0
+        assert d.nano == 0
+        assert d.zone == zone : 'cleartTime() should not change zone'
+    }
+
+    void testFormatByPattern() {
+        def zone =  ZoneId.of('America/New_York')
+        def offset = ZoneOffset.ofHours(2)
+
+        LocalDate ld = LocalDate.of(2018, Month.FEBRUARY, 13)
+        LocalTime lt = LocalTime.of(3,4,5,6_000_000)
+        LocalDateTime ldt = LocalDateTime.of(ld, lt)
+        OffsetTime ot = OffsetTime.of(lt, offset)
+        OffsetDateTime odt = OffsetDateTime.of(ldt, offset)
+        ZonedDateTime zdt = ZonedDateTime.of(ldt, zone)
+
+        assert ld.format('yyyy-MM-dd') == '2018-02-13'
+        assert lt.format('HH:mm:ss.SSS') == '03:04:05.006'
+        assert ldt.format('yyyy-MM-dd HH:mm:ss.SSS') == '2018-02-13 03:04:05.006'
+        assert ot.format('HH:mm:ss.SSS Z') == '03:04:05.006 +0200'
+        assert odt.format('yyyy-MM-dd HH:mm:ss.SSS Z') == '2018-02-13 03:04:05.006 +0200'
+        assert zdt.format('yyyy-MM-dd HH:mm:ss.SSS VV') == '2018-02-13 03:04:05.006 America/New_York'
+    }
+
+    void testLocalDateParse() {
+        LocalDate ld = LocalDate.parse('2018-02-15', 'yyyy-MM-dd')
+        assert [ld.year, ld.month, ld.dayOfMonth] == [2018, Month.FEBRUARY, 15]
+    }
+
+    void testLocalDateTimeParse() {
+        LocalDateTime ldt = LocalDateTime.parse('2018-02-15 21:43:03.002', 'yyyy-MM-dd HH:mm:ss.SSS')
+        assert [ldt.year, ldt.month, ldt.dayOfMonth] == [2018, Month.FEBRUARY, 15]
+        assert [ldt.hour, ldt.minute, ldt.second] == [21, 43, 03]
+        assert ldt.nano == 2 * 1e6
+    }
+
+    void testLocalTimeParse() {
+        LocalTime lt = LocalTime.parse('21:43:03.002', 'HH:mm:ss.SSS')
+        assert [lt.hour, lt.minute, lt.second] == [21, 43, 03]
+        assert lt.nano == 2 * 1e6
+    }
+
+    void testOffsetDateTimeParse() {
+        OffsetDateTime odt = OffsetDateTime.parse('2018-02-15 21:43:03.002 -00', 'yyyy-MM-dd HH:mm:ss.SSS X')
+        assert [odt.year, odt.month, odt.dayOfMonth] == [2018, Month.FEBRUARY, 15]
+        assert [odt.hour, odt.minute, odt.second] == [21, 43, 03]
+        assert odt.nano == 2 * 1e6
+        assert odt.offset.totalSeconds == 0
+    }
+
+    void testOffsetTimeParse() {
+        OffsetTime ot = OffsetTime.parse('21:43:03.002 -00', 'HH:mm:ss.SSS X')
+        assert [ot.hour, ot.minute, ot.second] == [21, 43, 03]
+        assert ot.nano == 2 * 1e6
+        assert ot.offset.totalSeconds == 0
+    }
+
+    void testZonedDateTimeParse() {
+        ZonedDateTime zdt = ZonedDateTime.parse('2018-02-15 21:43:03.002 UTC', 'yyyy-MM-dd HH:mm:ss.SSS z')
+        assert [zdt.year, zdt.month, zdt.dayOfMonth] == [2018, Month.FEBRUARY, 15]
+        assert [zdt.hour, zdt.minute, zdt.second] == [21, 43, 03]
+        assert zdt.nano == 2 * 1e6
+    }
+
+    void testPeriodBetweenYears() {
+        def period = Period.between(Year.of(2000), Year.of(2010))
+        assert period.years == 10
+        assert period.months == 0
+        assert period.days == 0
+    }
+
+    void testPeriodBetweenYearMonths() {
+        def period = Period.between(YearMonth.of(2018, Month.MARCH), YearMonth.of(2016, Month.APRIL))
+
+        assert period.years == -1
+        assert period.months == -11
+        assert period.days == 0
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/subprojects/groovy-datetime/src/test/java/org/apache/groovy/datetime/extensions/DateTimeExtensionsTest.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-datetime/src/test/java/org/apache/groovy/datetime/extensions/DateTimeExtensionsTest.java b/subprojects/groovy-datetime/src/test/java/org/apache/groovy/datetime/extensions/DateTimeExtensionsTest.java
new file mode 100644
index 0000000..4cbb8df
--- /dev/null
+++ b/subprojects/groovy-datetime/src/test/java/org/apache/groovy/datetime/extensions/DateTimeExtensionsTest.java
@@ -0,0 +1,94 @@
+/*
+ *  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.
+ */
+
+package org.apache.groovy.datetime.extensions;
+
+import org.junit.Test;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Month;
+import java.time.MonthDay;
+import java.time.Year;
+import java.time.YearMonth;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+import static org.junit.Assert.assertEquals;
+
+public class DateTimeExtensionsTest {
+    @Test
+    public void calendarConversionsDefaultTimeZone() throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HHmmss SSS");
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(sdf.parse("20180115 153256 001"));
+
+        LocalDate expectedLocalDate = LocalDate.of(2018, Month.JANUARY, 15);
+        LocalTime expectedLocalTime = LocalTime.of(15, 32, 56, 1_000_000);
+        LocalDateTime expectedLocalDateTime = LocalDateTime.of(expectedLocalDate, expectedLocalTime);
+
+        assertEquals("DayOfWeek", DayOfWeek.MONDAY, DateTimeExtensions.toDayOfWeek(calendar));
+        assertEquals("Month", Month.JANUARY, DateTimeExtensions.toMonth(calendar));
+        assertEquals("MonthDay", MonthDay.of(Month.JANUARY, 15), DateTimeExtensions.toMonthDay(calendar));
+        assertEquals("YearMonth", YearMonth.of(2018, Month.JANUARY), DateTimeExtensions.toYearMonth(calendar));
+        assertEquals("Year", Year.of(2018), DateTimeExtensions.toYear(calendar));
+        assertEquals("LocalDate", expectedLocalDate, DateTimeExtensions.toLocalDate(calendar));
+        assertEquals("LocalTime", expectedLocalTime, DateTimeExtensions.toLocalTime(calendar));
+        assertEquals("LocalDateTime", expectedLocalDateTime, DateTimeExtensions.toLocalDateTime(calendar));
+        assertEquals("OffsetTime", expectedLocalTime, DateTimeExtensions.toOffsetTime(calendar).toLocalTime());
+        assertEquals("OffsetDateTime", expectedLocalDateTime,
+                DateTimeExtensions.toOffsetDateTime(calendar).toLocalDateTime());
+        assertEquals("ZonedDateTime", expectedLocalDateTime,
+                DateTimeExtensions.toZonedDateTime(calendar).toLocalDateTime());
+    }
+
+    @Test
+    public void calendarConversionsDifferingTimeZones() throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HHmmss SSS");
+        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC+0"));
+        calendar.setTime(sdf.parse("20180115 153256 001"));
+    }
+
+    @Test
+    public void sameCalendarAndDateConvertIdentically() throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HHmmss SSS");
+        Date date = sdf.parse("20180115 153256 001");
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+
+        assertEquals("DayOfWeek", DateTimeExtensions.toDayOfWeek(calendar), DateTimeExtensions.toDayOfWeek(date));
+        assertEquals("Month", DateTimeExtensions.toMonth(calendar), DateTimeExtensions.toMonth(date));
+        assertEquals("MonthDay", DateTimeExtensions.toMonthDay(calendar), DateTimeExtensions.toMonthDay(date));
+        assertEquals("YearMonth", DateTimeExtensions.toYearMonth(calendar), DateTimeExtensions.toYearMonth(date));
+        assertEquals("Year", DateTimeExtensions.toYear(calendar), DateTimeExtensions.toYear(date));
+        assertEquals("LocalDate", DateTimeExtensions.toLocalDate(calendar), DateTimeExtensions.toLocalDate(date));
+        assertEquals("LocalTime", DateTimeExtensions.toLocalTime(calendar), DateTimeExtensions.toLocalTime(date));
+        assertEquals("LocalDateTime", DateTimeExtensions.toLocalDate(calendar), DateTimeExtensions.toLocalDate(date));
+        assertEquals("OffsetTime", DateTimeExtensions.toOffsetTime(calendar), DateTimeExtensions.toOffsetTime(date));
+        assertEquals("OffsetDateTime",
+                DateTimeExtensions.toOffsetDateTime(calendar), DateTimeExtensions.toOffsetDateTime(date));
+        assertEquals("ZonedDateTime",
+                DateTimeExtensions.toZonedDateTime(calendar), DateTimeExtensions.toZonedDateTime(date));
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/subprojects/groovy-dateutil/build.gradle
----------------------------------------------------------------------
diff --git a/subprojects/groovy-dateutil/build.gradle b/subprojects/groovy-dateutil/build.gradle
new file mode 100644
index 0000000..296f0fa
--- /dev/null
+++ b/subprojects/groovy-dateutil/build.gradle
@@ -0,0 +1,28 @@
+/*
+ *  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.
+ */
+dependencies {
+    compile rootProject
+    testCompile project(':groovy-test')
+}
+
+task moduleDescriptor(type: org.codehaus.groovy.gradle.WriteExtensionDescriptorTask) {
+    extensionClasses = 'org.apache.groovy.dateutil.extensions.DateUtilExtensions'
+//    staticExtensionClasses = 'org.apache.groovy.dateutil.extensions.DateUtilStaticExtensions'
+}
+compileJava.dependsOn moduleDescriptor


[05/14] groovy git commit: initial port of Goodtimes-inspired methods

Posted by pa...@apache.org.
initial port of Goodtimes-inspired methods


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/99db9bf8
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/99db9bf8
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/99db9bf8

Branch: refs/heads/GROOVY_2_6_X
Commit: 99db9bf868fb50c172cda0a282f58ed47b1b07e7
Parents: 235f9cc
Author: Joe Wolf <jo...@gmail.com>
Authored: Sun Feb 18 17:59:12 2018 -0500
Committer: paulk <pa...@asert.com.au>
Committed: Thu Mar 22 00:41:46 2018 +1000

----------------------------------------------------------------------
 gradle/docs.gradle                              |    1 +
 .../groovy/runtime/DateGroovyMethods.java       |  358 +++-
 .../groovy/runtime/DateTimeGroovyMethods.java   | 1905 ++++++++++++++++++
 .../groovy/runtime/DefaultGroovyMethods.java    |    1 +
 .../runtime/DefaultGroovyStaticMethods.java     |  174 +-
 src/test/groovy/DateTimeTest.groovy             |  680 +++++++
 .../groovy/runtime/DateGroovyMethodsTest.java   |   57 +
 7 files changed, 3163 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/99db9bf8/gradle/docs.gradle
----------------------------------------------------------------------
diff --git a/gradle/docs.gradle b/gradle/docs.gradle
index 449524f..9fb0ca7 100644
--- a/gradle/docs.gradle
+++ b/gradle/docs.gradle
@@ -142,6 +142,7 @@ task docGDK {
                     arg(value: 'org.codehaus.groovy.runtime.DefaultGroovyMethods')
                     arg(value: 'org.codehaus.groovy.runtime.DefaultGroovyStaticMethods')
                     arg(value: 'org.codehaus.groovy.runtime.DateGroovyMethods')
+                    arg(value: 'org.codehaus.groovy.runtime.DateTimeGroovyMethods')
                     arg(value: 'org.codehaus.groovy.runtime.EncodingGroovyMethods')
                     arg(value: 'org.codehaus.groovy.runtime.IOGroovyMethods')
                     arg(value: 'org.codehaus.groovy.runtime.ProcessGroovyMethods')

http://git-wip-us.apache.org/repos/asf/groovy/blob/99db9bf8/src/main/java/org/codehaus/groovy/runtime/DateGroovyMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/DateGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DateGroovyMethods.java
index 822bdc5..0190b35 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DateGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DateGroovyMethods.java
@@ -24,11 +24,8 @@ import groovy.lang.GroovyRuntimeException;
 import java.sql.Timestamp;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.TimeZone;
+import java.time.*;
+import java.util.*;
 
 /**
  * This class defines new groovy methods which appear on normal JDK
@@ -712,7 +709,7 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
                 closure.call(i);
             }
         } else
-            throw new GroovyRuntimeException("The argument (" + to + 
+            throw new GroovyRuntimeException("The argument (" + to +
                     ") to upto() cannot be earlier than the value (" + self + ") it's called on.");
     }
 
@@ -731,7 +728,7 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
                 closure.call(i);
             }
         } else
-            throw new GroovyRuntimeException("The argument (" + to + 
+            throw new GroovyRuntimeException("The argument (" + to +
                     ") to upto() cannot be earlier than the value (" + self + ") it's called on.");
     }
 
@@ -750,7 +747,7 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
                 closure.call(i);
             }
         } else
-            throw new GroovyRuntimeException("The argument (" + to + 
+            throw new GroovyRuntimeException("The argument (" + to +
                     ") to downto() cannot be later than the value (" + self + ") it's called on.");
     }
 
@@ -769,7 +766,350 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
                 closure.call(i);
             }
         } else
-            throw new GroovyRuntimeException("The argument (" + to + 
+            throw new GroovyRuntimeException("The argument (" + to +
                     ") to downto() cannot be later than the value (" + self + ") it's called on.");
     }
+
+    /**
+     * Returns the Time Zone offset of the Calendar as a {@link java.time.ZoneOffset}.
+     *
+     * @param self a Calendar
+     * @return a ZoneOffset
+     * @since 3.0
+     */
+    public static ZoneOffset getZoneOffset(final Calendar self) {
+        int offsetMillis = self.get(Calendar.ZONE_OFFSET) + self.get(Calendar.DST_OFFSET);
+        return ZoneOffset.ofTotalSeconds(offsetMillis / 1000);
+    }
+
+    /**
+     * Returns the Time Zone offset of the Date as a {@link java.time.ZoneOffset},
+     * which will typically be system's default offset.
+     *
+     * @param self a Date
+     * @return a ZoneOffset
+     * @since 3.0
+     */
+    public static ZoneOffset getZoneOffset(final Date self) {
+        return getZoneOffset(toCalendar(self));
+    }
+
+    /**
+     * Returns the Time Zone of the Calendar as a java.time.ZoneId.
+     *
+     * @param self a Calendar
+     * @return a ZoneId
+     * @since 3.0
+     */
+    public static ZoneId getZoneId(final Calendar self) {
+        return self.getTimeZone().toZoneId();
+    }
+
+    /**
+     * Returns the Time Zone of the Date as a {@link java.time.ZoneId}. This will
+     * typically be the system's default ZoneId.
+     *
+     * @param self a Date
+     * @return a ZoneId
+     * @since 3.0
+     */
+    public static ZoneId getZoneId(final Date self) {
+        return getZoneId(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.Year}.  If the Calendar has a different
+     * time zone than the system default, the Year will be adjusted into the default time zone.
+     *
+     * @param self a Calendar
+     * @return a Year
+     * @since 3.0
+     */
+    public static Year toYear(final Calendar self) {
+        return Year.of(self.get(Calendar.YEAR));
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.Year}.
+     *
+     * @param self a Date
+     * @return a Year
+     * @since 3.0
+     */
+    public static Year toYear(final Date self) {
+        return toYear(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.Month}. If the Calendar has a different
+     * time zone than the system default, the Month will be adjusted into the default time zone.
+     *
+     * @param self a Calendar
+     * @return a Month
+     * @since 3.0
+     */
+    public static Month toMonth(final Calendar self) {
+        return Month.of(self.get(Calendar.MONTH) + 1);
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.Month}.
+     *
+     * @param self a Date
+     * @return a Month
+     * @since 3.0
+     */
+    public static Month toMonth(final Date self) {
+        return toMonth(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.MonthDay}. If the Calendar has a different
+     * time zone than the system default, the MonthDay will be adjusted into the default time zone.
+     *
+     * @param self a Calendar
+     * @return a MonthDay
+     * @since 3.0
+     */
+    public static MonthDay toMonthDay(final Calendar self) {
+        return MonthDay.of(toMonth(self), self.get(Calendar.DAY_OF_MONTH));
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.MonthDay}.
+     *
+     * @param self a Date
+     * @return a MonthDay
+     * @since 3.0
+     */
+    public static MonthDay toMonthDay(final Date self) {
+        return toMonthDay(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.YearMonth}. If the Calendar has a different
+     * time zone than the system default, the YearMonth will be adjusted into the default time zone.
+     *
+     * @param self a Calendar
+     * @return a YearMonth
+     * @since 3.0
+     */
+    public static YearMonth toYearMonth(final Calendar self) {
+        return toYear(self).atMonth(toMonth(self));
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.YearMonth}.
+     *
+     * @param self a Date
+     * @return a YearMonth
+     * @since 3.0
+     */
+    public static YearMonth toYearMonth(final Date self) {
+        return toYearMonth(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.DayOfWeek}. If the Calendar has a different
+     * time zone than the system default, the DayOfWeek will be adjusted into the default time zone.
+     *
+     *
+     * @param self a Calendar
+     * @return a DayOfWeek
+     * @since 3.0
+     */
+    public static DayOfWeek toDayOfWeek(final Calendar self) {
+        return DayOfWeek.of(self.get(Calendar.DAY_OF_WEEK)).minus(1);
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.DayOfWeek}.
+     *
+     * @param self a Date
+     * @return a DayOfWeek
+     * @since 3.0
+     */
+    public static DayOfWeek toDayOfWeek(final Date self) {
+        return toDayOfWeek(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.LocalDate}. If the Calendar has a different
+     * time zone than the system default, the LocalDate will be adjusted into the default time zone.
+     *
+     * @param self a Calendar
+     * @return a LocalDate
+     * @since 3.0
+     */
+    static LocalDate toLocalDate(final Calendar self) {
+        return LocalDate.of(self.get(Calendar.YEAR), toMonth(self), self.get(Calendar.DAY_OF_MONTH));
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.LocalDate}.
+     *
+     * @param self a Date
+     * @return a LocalDate
+     * @since 3.0
+     */
+    public static LocalDate toLocalDate(final Date self) {
+        return toLocalDate(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.LocalTime}. If the Calendar has a different
+     * time zone than the system default, the LocalTime will be adjusted into the default time zone.
+     *
+     * @param self a Calendar
+     * @return a LocalTime
+     * @since 3.0
+     */
+    public static LocalTime toLocalTime(final Calendar self) {
+        int hour = self.get(Calendar.HOUR_OF_DAY);
+        int minute = self.get(Calendar.MINUTE);
+        int second = self.get(Calendar.SECOND);
+        int ns = self.get(Calendar.MILLISECOND) * 1_000_000;
+        return LocalTime.of(hour, minute, second, ns);
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.LocalTime}.
+     *
+     * @param self a Date
+     * @return a LocalTime
+     * @since 3.0
+     */
+    public static LocalTime toLocalTime(final Date self) {
+        return toLocalTime(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.LocalDateTime}. If the Calendar has a different
+     * time zone than the system default, the LocalDateTime will be adjusted into the default time zone.
+     *
+     * @param self a Calendar
+     * @return a LocalDateTime
+     * @since 3.0
+     */
+    public static LocalDateTime toLocalDateTime(final Calendar self) {
+        return LocalDateTime.of(toLocalDate(self), toLocalTime(self));
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.LocalDateTime}.
+     *
+     * @param self a Date
+     * @return a LocalDateTime
+     * @since 3.0
+     */
+    public static LocalDateTime toLocalDateTime(final Date self) {
+        return toLocalDateTime(toCalendar(self));
+    }
+
+    /**
+     * <p>Converts the Calendar to a corresponding {@link java.time.ZonedDateTime}.</p><p>Note that
+     * {@link java.util.GregorianCalendar} has a {@link java.util.GregorianCalendar#toZonedDateTime} method,
+     * which is commonly the specific type of Calendar in use.</p>
+     *
+     * @param self a Calendar
+     * @return a ZonedDateTime
+     * @since 3.0
+     */
+    public static ZonedDateTime toZonedDateTime(final Calendar self) {
+        if (self instanceof GregorianCalendar) { // would this branch ever be true?
+            return ((GregorianCalendar) self).toZonedDateTime();
+        } else {
+            return ZonedDateTime.of(toLocalDateTime(self), getZoneId(self));
+        }
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.ZonedDateTime}.
+     *
+     * @param self a Date
+     * @return a ZonedDateTime
+     * @since 3.0
+     */
+    public static ZonedDateTime toZonedDateTime(final Date self) {
+        return toZonedDateTime(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.OffsetDateTime}.
+     *
+     * @param self a Calendar
+     * @return an OffsetDateTime
+     * @since 3.0
+     */
+    public static OffsetDateTime toOffsetDateTime(final Calendar self) {
+        return OffsetDateTime.of(toLocalDateTime(self), getZoneOffset(self));
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.OffsetDateTime}.
+     *
+     * @param self a Date
+     * @return an OffsetDateTime
+     * @since 3.0
+     */
+    public static OffsetDateTime toOffsetDateTime(final Date self) {
+        return toOffsetDateTime(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.OffsetTime}.
+     *
+     * @param self a Calendar
+     * @return an OffsetTime
+     * @since 3.0
+     */
+    public static OffsetTime toOffsetTime(final Calendar self) {
+        return OffsetTime.of(toLocalTime(self), getZoneOffset(self));
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.OffsetTime}.
+     *
+     * @param self a Date
+     * @return an OffsetTime
+     * @since 3.0
+     */
+    public static OffsetTime toOffsetTime(final Date self) {
+        return toOffsetTime(toCalendar(self));
+    }
+
+    /**
+     * Convenience method for converting a Calendar to a corresponding {@link java.time.Instant}.
+     *
+     * @param self a Calendar
+     * @return an Instant
+     * @since 3.0
+     */
+    public static Instant toInstant(final Calendar self) {
+        return self.getTime().toInstant();
+    }
+
+    /**
+     * Converts the TimeZone to a corresponding {@link java.time.ZoneOffset}. The offset is determined
+     * using the current date/time.
+     *
+     * @param self a TimeZone
+     * @return a ZoneOffset
+     * @since 3.0
+     */
+    public static ZoneOffset toZoneOffset(final TimeZone self) {
+        return toZoneOffset(self, Instant.now());
+    }
+
+    /**
+     * Converts this TimeZone to a corresponding {@link java.time.ZoneOffset}. The offset is determined
+     * using the date/time of specified Instant.
+     *
+     * @param self a TimeZone
+     * @return a ZoneOffset
+     * @since 3.0
+     */
+    public static ZoneOffset toZoneOffset(final TimeZone self, Instant instant) {
+        return self.toZoneId().getRules().getOffset(instant);
+    }
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/99db9bf8/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
new file mode 100644
index 0000000..947e368
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
@@ -0,0 +1,1905 @@
+/*
+ *  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.
+ */
+package org.codehaus.groovy.runtime;
+
+import groovy.lang.Closure;
+import groovy.lang.GroovyRuntimeException;
+
+import java.time.*;
+import java.time.chrono.ChronoLocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
+import java.time.format.TextStyle;
+import java.time.temporal.*;
+import java.util.*;
+
+/**
+ * This class defines new Groovy methods which appear on normal JDK
+ * Date/Time API (java.time) classes inside the Groovy environment.
+ *
+ * @author Joe Wolf
+ */
+public class DateTimeGroovyMethods {
+
+    // Static methods only
+    private DateTimeGroovyMethods() {
+    }
+
+    /**
+     * For any Temporal subtype that does not use {@link java.time.temporal.ChronoUnit#SECONDS} as the unit for
+     * the upto/downto methods, should have an entry.
+     */
+    private static Map<Class<? extends Temporal>, TemporalUnit> DEFAULT_UNITS = new HashMap<>();
+    static {
+        DEFAULT_UNITS.put(ChronoLocalDate.class, ChronoUnit.DAYS);
+        DEFAULT_UNITS.put(YearMonth.class, ChronoUnit.MONTHS);
+        DEFAULT_UNITS.put(Year.class, ChronoUnit.YEARS);
+    }
+
+    /**
+     * A number of extension methods permit a long or int to be provided as a parameter. This method determines
+     * what the unit should be for this number.
+     */
+    private static TemporalUnit defaultUnitFor(Temporal temporal) {
+        return DEFAULT_UNITS.entrySet()
+                .stream()
+                .filter(e -> e.getKey().isAssignableFrom(temporal.getClass()))
+                .findFirst()
+                .map(Map.Entry::getValue)
+                .orElse(ChronoUnit.SECONDS);
+    }
+
+    /**
+     * Truncates a nanosecond value to milliseconds. No rounding.
+     */
+    private static int millisFromNanos(int nanos) {
+       return nanos / 1_000_000;
+    }
+
+    /* ******** java.time.temporal.Temporal extension methods ******** */
+
+    /**
+     * Iterates from the this to {@code to}, inclusive, incrementing by one unit each iteration, calling the
+     * closure once per iteration. The closure may accept a single {@link java.time.temporal.Temporal} argument.
+     * <p>
+     * The particular unit incremented by depends on the specific sub-type of {@link java.time.temporal.Temporal}.
+     * Most sub-types use a unit of {@link java.time.temporal.ChronoUnit#SECONDS} except for
+     * <ul>
+     * <li>{@link java.time.chrono.ChronoLocalDate} and its sub-types use {@link java.time.temporal.ChronoUnit#DAYS}.
+     * <li>{@link java.time.YearMonth} uses {@link java.time.temporal.ChronoUnit#MONTHS}.
+     * <li>{@link java.time.Year} uses {@link java.time.temporal.ChronoUnit#YEARS}.
+     * </ul>
+     *
+     * @param from the starting Temporal
+     * @param to the ending Temporal
+     * @param closure the zero or one-argument closure to call
+     * @throws GroovyRuntimeException if this value is later than {@code to}
+     * @since 3.0
+     */
+    public static void upto(Temporal from, Temporal to, Closure closure) {
+        upto(from, to, defaultUnitFor(from), closure);
+    }
+
+    /**
+     * Iterates from this to {@code to}, inclusive, incrementing by one {@code unit} each iteration,
+     * calling the closure once per iteration. The closure may accept a single
+     * {@link java.time.temporal.Temporal} argument.
+     *
+     * If the unit is too large to iterate to the second Temporal exactly, such as iterating from two LocalDateTimes
+     * that are seconds apart using {@java.time.temporal.ChronoUnit#DAYS} as the unit, the iteration will cease
+     * as soon as the current value of the iteration is later than the second Temporal argument.
+     *
+     * @param from the starting Temporal
+     * @param to   the ending Temporal
+     * @param unit the TemporalUnit to increment by
+     * @param closure the zero or one-argument closure to call
+     * @throws GroovyRuntimeException if this value is later than {@code to}
+     * @since 3.0
+     */
+    public static void upto(Temporal from, Temporal to, TemporalUnit unit, Closure closure) {
+        if (from.until(to, unit) >= 0) {
+            for (Temporal i = from; i.until(to, unit) >= 0; i = i.plus(1, unit)) {
+                closure.call(i);
+            }
+        } else {
+            throw new GroovyRuntimeException("The argument (" + to +
+                    ") to upto() cannot be earlier than the value (" + from + ") it's called on.");
+        }
+    }
+
+    /**
+     * Iterates from this to the {@code to} {@link java.time.temporal.Temporal}, inclusive, decrementing by one
+     * unit each iteration, calling the closure once per iteration. The closure may accept a single
+     * {@link java.time.temporal.Temporal} argument.
+     *
+     * The particular unit decremented by depends on the specific sub-type of {@link java.time.temporal.Temporal}.
+     * Most sub-types use a unit of {@link java.time.temporal.ChronoUnit#SECONDS} except for
+     * <ul>
+     * <li>{@link java.time.chrono.ChronoLocalDate} and its sub-types use {@link java.time.temporal.ChronoUnit#DAYS}.
+     * <li>{@link java.time.YearMonth} uses {@link java.time.temporal.ChronoUnit#MONTHS}.
+     * <li>{@link java.time.Year} uses {@link java.time.temporal.ChronoUnit#YEARS}.
+     * </ul>
+     *
+     * @param from the starting Temporal
+     * @param to the ending Temporal
+     * @param closure the zero or one-argument closure to call
+     * @throws GroovyRuntimeException if this value is earlier than {@code to}
+     * @since 3.0
+     */
+    public static void downto(Temporal from, Temporal to, Closure closure) {
+        downto(from, to, defaultUnitFor(from), closure);
+    }
+
+    /**
+     * Iterates from this to the {@code to} {@link java.time.temporal.Temporal}, inclusive, decrementing by one
+     * {@code unit} each iteration, calling the closure once per iteration. The closure may accept a single
+     * {@link java.time.temporal.Temporal} argument.
+     *
+     * If the unit is too large to iterate to the second Temporal exactly, such as iterating from two LocalDateTimes
+     * that are seconds apart using {@java.time.temporal.ChronoUnit#DAYS} as the unit, the iteration will cease
+     * as soon as the current value of the iteration is earlier than the second Temporal argument.
+     *
+     * @param from the starting Temporal
+     * @param to   the ending Temporal
+     * @param unit the TemporalUnit to increment by
+     * @param closure the zero or one-argument closure to call
+     * @throws GroovyRuntimeException if this value is earlier than {@code to}
+     * @since 3.0
+     */
+    public static void downto(Temporal from, Temporal to, TemporalUnit unit, Closure closure) {
+        if (from.until(to, unit) <= 0) {
+            for (Temporal i = from; i.until(to, unit) <= 0; i = i = i.minus(1, unit)) {
+                closure.call(i);
+            }
+        } else {
+            throw new GroovyRuntimeException("The argument (" + to +
+                    ") to downto() cannot be later than the value (" + from + ") it's called on.");
+        }
+    }
+
+    /**
+     * Returns a {@link java.time.Duration} of time between this (inclusive) and {@code other} (exclusive).
+     *
+     * @param self  a Temporal
+     * @param other another Temporal
+     * @return an Duration between the two Instants
+     * @since 3.0
+     */
+    public static Duration rightShift(final Temporal self, Temporal other) {
+        return Duration.between(self, other);
+    }
+
+    /* ******** java.time.temporal.TemporalAccessor extension methods ******** */
+
+    /**
+     * Supports the getAt operator; equivalent to calling the
+     * {@link java.time.temporal.TemporalAccessor#getLong(java.time.temporal.TemporalField)} method.
+     *
+     * @param self  a TemporalAccessor
+     * @param field a non-null TemporalField
+     * @return the value for the field
+     * @throws DateTimeException if a value for the field cannot be obtained
+     * @throws UnsupportedTemporalTypeException if the field is not supported
+     * @throws ArithmeticException if numeric overflow occurs
+     * @since 3.0
+     */
+    public static long getAt(final TemporalAccessor self, TemporalField field) {
+        return self.getLong(field);
+    }
+
+    /* ******** java.time.temporal.TemporalAmount extension methods ******** */
+
+    /**
+     * Supports the getAt operator; equivalent to calling the
+     * {@link java.time.temporal.TemporalAmount#get(TemporalUnit)} method.
+     *
+     * @param self  a TemporalAmount
+     * @param unit  a non-null TemporalUnit
+     * @return the value for the field
+     * @throws DateTimeException if a value for the field cannot be obtained
+     * @throws UnsupportedTemporalTypeException if the field is not supported
+     * @throws ArithmeticException if numeric overflow occurs
+     * @since 3.0
+     */
+    public static long getAt(final TemporalAmount self, TemporalUnit unit) {
+        return self.get(unit);
+    }
+
+    /* ******** java.time.Duration extension methods ******** */
+
+    /**
+     * Returns a {@link java.time.Duration} that is {@code seconds} seconds longer than this duration.
+     *
+     * @param self    a Duration
+     * @param seconds the number of seconds to add
+     * @return a Duration
+     * @since 3.0
+     */
+    public static Duration plus(final Duration self, long seconds) {
+        return self.plusSeconds(seconds);
+    }
+
+    /**
+     * Returns a {@link java.time.Duration} that is {@code seconds} seconds shorter that this duration.
+     *
+     * @param self    a Duration
+     * @param seconds the number of seconds to subtract
+     * @return a Duration
+     * @since 3.0
+     */
+    public static Duration minus(final Duration self, long seconds) {
+        return self.minusSeconds(seconds);
+    }
+
+    /**
+     * Returns a {@link java.time.Duration} that is one second longer than this duration.
+     *
+     * @param self a Duration
+     * @return a Duration
+     * @since 3.0
+     */
+    public static Duration next(final Duration self) {
+        return self.plusSeconds(1);
+    }
+
+    /**
+     * Returns a {@link java.time.Duration} that is one second shorter than this duration.
+     *
+     * @param self a Duration
+     * @return a Duration
+     * @since 3.0
+     */
+    public static Duration previous(final Duration self) {
+        return self.minusSeconds(1);
+    }
+
+    /**
+     * Supports the unary minus operator; equivalent to calling the {@link Duration#negated()} method.
+     *
+     * @param self a Duration
+     * @return a Duration
+     * @since 3.0
+     */
+    public static Duration negative(final Duration self) {
+        return self.negated();
+    }
+
+    /**
+     * Supports the unary plus operator; equivalent to calling the {@link Duration#abs()} method.
+     *
+     * @param self a Duration
+     * @return a Duration
+     * @since 3.0
+     */
+    public static Duration positive(final Duration self) {
+        return self.abs();
+    }
+
+    /**
+     * Supports the multiplication operator; equivalent to calling the {@link Duration#multipliedBy(long)} method.
+     *
+     * @param self   a Duration
+     * @param scalar the value to multiply by
+     * @return a Duration
+     * @since 3.0
+     */
+    public static Duration multiply(final Duration self, long scalar) {
+        return self.multipliedBy(scalar);
+    }
+
+    /**
+     * Supports the division operator; equivalent to calling the {@link Duration#dividedBy(long)} method.
+     *
+     * @param self   a Duration
+     * @param scalar the value to divide by
+     * @return a Duration
+     * @since 3.0
+     */
+    public static Duration div(final Duration self, long scalar) {
+        return self.dividedBy(scalar);
+    }
+
+    /* ******** java.time.Instant extension methods ******** */
+
+    /**
+     * Returns an {@link java.time.Instant} that is {@code seconds} seconds after this instant.
+     *
+     * @param self    an Instant
+     * @param seconds the number of seconds to add
+     * @return an Instant
+     * @since 3.0
+     */
+    public static Instant plus(final Instant self, long seconds) {
+        return self.plusSeconds(seconds);
+    }
+
+    /**
+     * Returns an {@link java.time.Instant} that is {@code seconds} seconds before this instant.
+     *
+     * @param self    an Instant
+     * @param seconds the number of seconds to subtract
+     * @return an Instant
+     * @since 3.0
+     */
+    public static Instant minus(final Instant self, long seconds) {
+        return self.minusSeconds(seconds);
+    }
+
+    /**
+     * Returns an {@link java.time.Instant} that is one second after this instant.
+     *
+     * @param self an Instant
+     * @return an Instant one second ahead
+     * @since 3.0
+     */
+    public static Instant next(final Instant self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns an {@link java.time.Instant} that one second before this instant.
+     *
+     * @param self an Instant
+     * @return an Instant one second behind
+     * @since 3.0
+     */
+    public static Instant previous(final Instant self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns a generally equivalent {@link java.util.Date} according the number of milliseconds since the epoch,
+     * adjusted into the system default time zone.
+     *
+     * @param self an Instant
+     * @return a Date
+     * @since 3.0
+     */
+    public static Date toDate(final Instant self) {
+        return new Date(self.toEpochMilli());
+    }
+
+    /**
+     * Returns a generally equivalent {@link java.util.Calendar} in the GMT time zone, truncated to milliseconds.
+     *
+     * @param self an Instant
+     * @return a Calendar
+     * @since 3.0
+     */
+    public static Calendar toCalendar(final Instant self) {
+        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+        cal.setTime(toDate(self));
+        return cal;
+    }
+
+    /* ******** java.time.LocalDate extension methods ******** */
+
+    /**
+     * Formats this date with the provided {@link java.time.format.DateTimeFormatter} pattern.
+     *
+     * @param self    a LocalDate
+     * @param pattern the formatting pattern
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String format(final LocalDate self, String pattern) {
+        return self.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Formats this date in the provided, localized {@link java.time.format.FormatStyle}.
+     *
+     * @param self      a LocalDate
+     * @param dateStyle the FormatStyle
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String format(final LocalDate self, FormatStyle dateStyle) {
+        return self.format(DateTimeFormatter.ofLocalizedDate(dateStyle));
+    }
+
+    /**
+     * Formats this date in the locale-specific {@link java.time.format.FormatStyle#SHORT} format style.
+     *
+     * @param self a LocalDate
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String getDateString(final LocalDate self) {
+        return format(self, FormatStyle.SHORT);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDate} that is {@code days} days after this date.
+     *
+     * @param self a LocalDate
+     * @param days the number of days to add
+     * @return a LocalDate
+     * @since 3.0
+     */
+    public static LocalDate plus(final LocalDate self, long days) {
+        return self.plusDays(days);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDate} that is {@code days} days before this date.
+     *
+     * @param self a LocalDate
+     * @param days the number of days to subtract
+     * @return a LocalDate
+     * @since 3.0
+     */
+    public static LocalDate minus(final LocalDate self, long days) {
+        return self.minusDays(days);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDate} one day after this date.
+     *
+     * @param self a LocalDate
+     * @return the next day
+     * @since 3.0
+     */
+    public static LocalDate next(final LocalDate self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDate} one day before this date.
+     *
+     * @param self a LocalDate
+     * @return the previous day
+     * @since 3.0
+     */
+    public static LocalDate previous(final LocalDate self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.Period} equivalent to the time between this date (inclusive)
+     * and the provided {@link java.time.LocalDate} (exclusive).
+     *
+     * @param self  a LocalDate
+     * @param other another LocalDate
+     * @return a Period representing the time between the two LocalDates
+     * @since 3.0
+     */
+    public static Period rightShift(final LocalDate self, LocalDate other) {
+        return Period.between(self, other);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDateTime} from this date and the provided {@link java.time.LocalTime}.
+     *
+     * @param self a LocalDate
+     * @param time a LocalTime
+     * @return a LocalDateTime
+     * @since 3.0
+     */
+    public static LocalDateTime leftShift(final LocalDate self, LocalTime time) {
+        return LocalDateTime.of(self, time);
+    }
+
+    /**
+     * Returns a {@link java.time.OffsetDateTime} from this date and the provided {@link java.time.OffsetTime}.
+     *
+     * @param self a LocalDate
+     * @param time an OffsetTime
+     * @return an OffsetDateTime
+     * @since 3.0
+     */
+    public static OffsetDateTime leftShift(final LocalDate self, OffsetTime time) {
+        return time.atDate(self);
+    }
+
+    /**
+     * Returns an equivalent instance of {@link java.util.Date}.
+     * The time portion of the returned date is cleared.
+     *
+     * @param self a LocalDate
+     * @return a java.util.Date
+     * @since 3.0
+     */
+    public static Date toDate(final LocalDate self) {
+        return toCalendar(self).getTime();
+    }
+
+    /**
+     * Returns an equivalent instance of {@link java.util.Calendar}.
+     * The time portion of the returned calendar is cleared and the time zone is the current system default.
+     *
+     * @param self a LocalDate
+     * @return a java.util.Calendar
+     * @since 3.0
+     */
+    public static Calendar toCalendar(final LocalDate self) {
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.DATE, self.getDayOfMonth());
+        cal.set(Calendar.MONTH, self.getMonthValue() - 1);
+        cal.set(Calendar.YEAR, self.getYear());
+        return DateGroovyMethods.clearTime(cal);
+    }
+
+    /* ******** java.time.LocalDateTime extension methods ******** */
+
+    /**
+     * Formats this date/time with the provided {@link java.time.format.DateTimeFormatter} pattern.
+     *
+     * @param self    a LocalDateTime
+     * @param pattern the formatting pattern
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String format(final LocalDateTime self, String pattern) {
+        return self.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Formats this date/time in the provided, localized {@link java.time.format.FormatStyle}.
+     *
+     * @param self          a LocalDateTime
+     * @param dateTimeStyle the FormatStyle
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String format(final LocalDateTime self, FormatStyle dateTimeStyle) {
+        return self.format(DateTimeFormatter.ofLocalizedDateTime(dateTimeStyle));
+    }
+
+    /**
+     * Formats this date/time in the locale-specific {@link java.time.format.FormatStyle#SHORT} format style.
+     *
+     * @param self a LocalDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String getDateTimeString(final LocalDateTime self) {
+        return format(self, FormatStyle.SHORT);
+    }
+
+    /**
+     * Formats the date portion of this date/time in the locale-specific
+     * {@link java.time.format.FormatStyle#SHORT} format style.
+     *
+     * @param self a LocalDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String getDateString(final LocalDateTime self) {
+        return getDateString(self.toLocalDate());
+    }
+
+    /**
+     * Formats the time portion of this date/time in the locale-specific
+     * {@link java.time.format.FormatStyle#SHORT} format style.
+     *
+     * @param self a LocalDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String getTimeString(final LocalDateTime self) {
+        return getTimeString(self.toLocalTime());
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDateTime} with the time portion cleared.
+     *
+     * @param self a LocalDateTime
+     * @return a LocalDateTime
+     * @since 3.0
+     */
+    public static LocalDateTime clearTime(final LocalDateTime self) {
+        return self.truncatedTo(ChronoUnit.DAYS);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDateTime} that is {@code seconds} seconds after this date/time.
+     *
+     * @param self    a LocalDateTime
+     * @param seconds the number of seconds to add
+     * @return a LocalDateTime
+     * @since 3.0
+     */
+    public static LocalDateTime plus(final LocalDateTime self, long seconds) {
+        return self.plusSeconds(seconds);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDateTime} that is {@code seconds} seconds before this date/time.
+     *
+     * @param self    a LocalDateTime
+     * @param seconds the number of seconds to subtract
+     * @return a LocalDateTime
+     * @since 3.0
+     */
+    public static LocalDateTime minus(final LocalDateTime self, long seconds) {
+        return self.minusSeconds(seconds);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDateTime} that is one second after this date/time.
+     *
+     * @param self a LocalDateTime
+     * @return a LocalDateTime
+     * @since 3.0
+     */
+    public static LocalDateTime next(final LocalDateTime self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDateTime} that is one second before this date/time.
+     *
+     * @param self a LocalDateTime
+     * @return a LocalDateTime
+     * @since 3.0
+     */
+    public static LocalDateTime previous(final LocalDateTime self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} of this date/time and the provided {@link java.time.ZoneOffset}.
+     *
+     * @param self   a LocalDateTime
+     * @param offset a ZoneOffset
+     * @return an OffsetDateTime
+     * @since 3.0
+     */
+    public static OffsetDateTime leftShift(final LocalDateTime self, ZoneOffset offset) {
+        return OffsetDateTime.of(self, offset);
+    }
+
+    /**
+     * Returns a {@link java.time.OffsetDateTime} of this date/time and the provided {@link java.time.ZoneId}.
+     *
+     * @param self a LocalDateTime
+     * @param zone a ZoneId
+     * @return a ZonedDateTime
+     * @since 3.0
+     */
+    public static ZonedDateTime leftShift(final LocalDateTime self, ZoneId zone) {
+        return ZonedDateTime.of(self, zone);
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Date}.
+     * The time value of the returned date is truncated to milliseconds.
+     *
+     * @param self a LocalDateTime
+     * @return a java.util.Date
+     * @since 3.0
+     */
+    public static Date toDate(final LocalDateTime self) {
+        return toCalendar(self).getTime();
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Calendar}.
+     * The time value of the returned calendar is truncated to milliseconds and the
+     * time zone is the current system default.
+     *
+     * @param self a LocalDateTime
+     * @return a java.util.Calendar
+     * @since 3.0
+     */
+    public static Calendar toCalendar(final LocalDateTime self) {
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.DATE, self.getDayOfMonth());
+        cal.set(Calendar.MONTH, self.getMonthValue() - 1);
+        cal.set(Calendar.YEAR, self.getYear());
+        cal.set(Calendar.HOUR_OF_DAY, self.getHour());
+        cal.set(Calendar.MINUTE, self.getMinute());
+        cal.set(Calendar.SECOND, self.getSecond());
+        cal.set(Calendar.MILLISECOND, millisFromNanos(self.getNano()));
+        return cal;
+    }
+
+    /* ******** java.time.LocalTime extension methods ******** */
+
+    /**
+     * Formats this time with the provided {@link java.time.format.DateTimeFormatter} pattern.
+     *
+     * @param self    a LocalDateTime
+     * @param pattern the formatting pattern
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String format(final LocalTime self, String pattern) {
+        return self.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Formats this time in the provided, localized {@link java.time.format.FormatStyle}.
+     *
+     * @param self      a LocalTime
+     * @param timeStyle the FormatStyle
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String format(final LocalTime self, FormatStyle timeStyle) {
+        return self.format(DateTimeFormatter.ofLocalizedTime(timeStyle));
+    }
+
+    /**
+     * Formats this time in the locale-specific {@link java.time.format.FormatStyle#SHORT} format style.
+     *
+     * @param self a LocalTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String getTimeString(final LocalTime self) {
+        return format(self, FormatStyle.SHORT);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalTime} that is {@code seconds} seconds after this time.
+     *
+     * @param self    a LocalTime
+     * @param seconds the number of seconds to add
+     * @return a LocalTime
+     * @since 3.0
+     */
+    public static LocalTime plus(final LocalTime self, long seconds) {
+        return self.plusSeconds(seconds);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalTime} that is {@code seconds} seconds before this time.
+     *
+     * @param self    a LocalTime
+     * @param seconds the number of seconds to subtract
+     * @return a LocalTime
+     * @since 3.0
+     */
+    public static LocalTime minus(final LocalTime self, long seconds) {
+        return self.minusSeconds(seconds);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalTime} that is one second after this time.
+     *
+     * @param self a LocalTime
+     * @return a LocalTime
+     * @since 3.0
+     */
+    public static LocalTime next(final LocalTime self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalTime} that is one second before this time.
+     *
+     * @param self a LocalTime
+     * @return a LocalTime
+     * @since 3.0
+     */
+    public static LocalTime previous(final LocalTime self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDateTime} of this time and the provided {@link java.time.LocalDate}.
+     *
+     * @param self a LocalTime
+     * @param date a LocalDate
+     * @return a LocalDateTime
+     * @since 3.0
+     */
+    public static LocalDateTime leftShift(final LocalTime self, LocalDate date) {
+        return LocalDateTime.of(date, self);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetTime} of this time and the provided {@link java.time.ZoneOffset}.
+     *
+     * @param self   a LocalTime
+     * @param offset a ZoneOffset
+     * @return an OffsetTime
+     * @since 3.0
+     */
+    public static OffsetTime leftShift(final LocalTime self, ZoneOffset offset) {
+        return OffsetTime.of(self, offset);
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Date}. The day-month-year value of the
+     * returned date is today and the time is truncated to milliseconds.
+     *
+     * @param self a LocalTime
+     * @return a java.util.Date
+     * @since 3.0
+     */
+    public static Date toDate(final LocalTime self) {
+        return toCalendar(self).getTime();
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Calendar}. The day-month-year value of the
+     * returned calendar is today, the time is truncated to milliseconds, and the time zone is the current
+     * system default.
+     *
+     * @param self a LocalTime
+     * @return a java.util.Calendar
+     * @since 3.0
+     */
+    public static Calendar toCalendar(final LocalTime self) {
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.HOUR_OF_DAY, self.getHour());
+        cal.set(Calendar.MINUTE, self.getMinute());
+        cal.set(Calendar.SECOND, self.getSecond());
+        cal.set(Calendar.MILLISECOND, millisFromNanos(self.getNano()));
+        return cal;
+    }
+
+    /* ******** java.time.MonthDay extension methods ******** */
+
+    /**
+     * Returns a {@link java.time.LocalDate} of this month/day and the provided year.
+     *
+     * @param self a MonthDay
+     * @param year a year
+     * @return a LocalDate
+     * @since 3.0
+     */
+    public static LocalDate leftShift(final MonthDay self, int year) {
+        return self.atYear(year);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDate} of this month/day and the provided {@link java.time.Year}.
+     *
+     * @param self a MonthDay
+     * @param year a Year
+     * @return a LocalDate
+     * @since 3.0
+     */
+    public static LocalDate leftShift(final MonthDay self, Year year) {
+        return year.atMonthDay(self);
+    }
+
+    /* ******** java.time.OffsetDateTime extension methods ******** */
+
+    /**
+     * Formats this date/time with the provided {@link java.time.format.DateTimeFormatter} pattern.
+     *
+     * @param self    an OffsetDateTime
+     * @param pattern the formatting pattern
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String format(final OffsetDateTime self, String pattern) {
+        return self.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Formats this date/time in the provided, localized {@link java.time.format.FormatStyle}.
+     *
+     * @param self          an OffsetDateTime
+     * @param dateTimeStyle the FormatStyle
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String format(final OffsetDateTime self, FormatStyle dateTimeStyle) {
+        return self.format(DateTimeFormatter.ofLocalizedDateTime(dateTimeStyle));
+    }
+
+    /**
+     * Formats this date/time in the locale-specific {@link java.time.format.FormatStyle#SHORT} format style.
+     *
+     * @param self an OffsetDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String getDateTimeString(final OffsetDateTime self) {
+        return format(self, FormatStyle.SHORT);
+    }
+
+    /**
+     * Formats the date portion of this date/time in the locale-specific
+     * {@link java.time.format.FormatStyle#SHORT} format style.
+     *
+     * @param self an OffsetDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String getDateString(final OffsetDateTime self) {
+        return getDateString(self.toLocalDate());
+    }
+
+    /**
+     * Formats the time portion of this date/time in the locale-specific
+     * {@link java.time.format.FormatStyle#SHORT} format style.
+     *
+     * @param self an OffsetDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String getTimeString(final OffsetDateTime self) {
+        return getTimeString(self.toLocalTime());
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} with the time portion cleared.
+     *
+     * @param self an OffsetDateTime
+     * @return an OffsetDateTime
+     * @since 3.0
+     */
+    public static OffsetDateTime clearTime(final OffsetDateTime self) {
+        return self.truncatedTo(ChronoUnit.DAYS);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} that is {@code seconds} seconds after this date/time.
+     *
+     * @param self    an OffsetDateTime
+     * @param seconds the number of seconds to add
+     * @return an OffsetDateTime
+     * @since 3.0
+     */
+    public static OffsetDateTime plus(final OffsetDateTime self, long seconds) {
+        return self.plusSeconds(seconds);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} that is {@code seconds} seconds before this date/time.
+     *
+     * @param self    an OffsetDateTime
+     * @param seconds the number of seconds to subtract
+     * @return an OffsetDateTime
+     * @since 3.0
+     */
+    public static OffsetDateTime minus(final OffsetDateTime self, long seconds) {
+        return self.minusSeconds(seconds);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} one second after this date/time.
+     *
+     * @param self an OffsetDateTime
+     * @return an OffsetDateTime
+     * @since 3.0
+     */
+    public static OffsetDateTime next(final OffsetDateTime self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} one second before this date/time.
+     *
+     * @param self an OffsetDateTime
+     * @return an OffsetDateTime
+     * @since 3.0
+     */
+    public static OffsetDateTime previous(final OffsetDateTime self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Date}.
+     * The time value of the returned date is truncated to milliseconds and will be
+     * adjusted to the current system default time zone.
+     *
+     * @param self an OffsetDateTime
+     * @return a java.util.Date
+     * @since 3.0
+     */
+    public static Date toDate(final OffsetDateTime self) {
+        return toCalendar(self).getTime();
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Calendar}.
+     * The time value of the returned calendar is truncated to milliseconds and the time zone
+     * is based on the offset of this date/time.
+     *
+     * @param self an OffsetDateTime
+     * @return a java.util.Calendar
+     * @since 3.0
+     */
+    public static Calendar toCalendar(final OffsetDateTime self) {
+        return toCalendar(self.toZonedDateTime());
+    }
+
+    /* ******** java.time.OffsetTime extension methods ******** */
+
+    /**
+     * Formats this time with the provided {@link java.time.format.DateTimeFormatter} pattern.
+     *
+     * @param self    an OffsetTime
+     * @param pattern the formatting pattern
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String format(final OffsetTime self, String pattern) {
+        return self.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Formats this time in the provided, localized {@link java.time.format.FormatStyle}.
+     *
+     * @param self      an OffsetTime
+     * @param timeStyle the FormatStyle
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String format(final OffsetTime self, FormatStyle timeStyle) {
+        return self.format(DateTimeFormatter.ofLocalizedTime(timeStyle));
+    }
+
+    /**
+     * Formats this time in the locale-specific {@link java.time.format.FormatStyle#SHORT} format style.
+     *
+     * @param self an OffsetTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String getTimeString(final OffsetTime self) {
+        return format(self, FormatStyle.SHORT);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetTime} that is {@code seconds} seconds after this time.
+     *
+     * @param self    an OffsetTime
+     * @param seconds the number of seconds to add
+     * @return an OffsetTime
+     * @since 3.0
+     */
+    public static OffsetTime plus(final OffsetTime self, long seconds) {
+        return self.plusSeconds(seconds);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetTime} that is {@code seconds} seconds before this time.
+     *
+     * @param self    an OffsetTime
+     * @param seconds the number of seconds to subtract
+     * @return an OffsetTime
+     * @since 3.0
+     */
+    public static OffsetTime minus(final OffsetTime self, long seconds) {
+        return self.minusSeconds(seconds);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetTime} that is one second after this time.
+     *
+     * @param self an OffsetTime
+     * @return an OffsetTime
+     * @since 3.0
+     */
+    public static OffsetTime next(final OffsetTime self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetTime} that is one second before this time.
+     *
+     * @param self an OffsetTime
+     * @return an OffsetTime
+     * @since 3.0
+     */
+    public static OffsetTime previous(final OffsetTime self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} of this time and the provided {@link java.time.LocalDate}.
+     *
+     * @param self an OffsetTime
+     * @param date a LocalDate
+     * @return an OffsetDateTime
+     * @since 3.0
+     */
+    public static OffsetDateTime leftShift(final OffsetTime self, LocalDate date) {
+        return OffsetDateTime.of(date, self.toLocalTime(), self.getOffset());
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Date}.
+     * The time value of the returned date is truncated to milliseconds and will be
+     * adjusted to the current system default time zone.
+     *
+     * @param self an OffsetTime
+     * @return a java.util.Date
+     * @since 3.0
+     */
+    public static Date toDate(final OffsetTime self) {
+        return toCalendar(self).getTime();
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Calendar}.
+     * The date value of the returned calendar is now, the time value is truncated to milliseconds,
+     * and the time zone is based on the offset of this time.
+     *
+     * @param self an OffsetTime
+     * @return a java.util.Calendar
+     * @since 3.0
+     */
+    public static Calendar toCalendar(final OffsetTime self) {
+        TimeZone timeZone = toTimeZone(self.getOffset());
+        Calendar cal = Calendar.getInstance(timeZone);
+        cal.set(Calendar.HOUR_OF_DAY, self.getHour());
+        cal.set(Calendar.MINUTE, self.getMinute());
+        cal.set(Calendar.SECOND, self.getSecond());
+        cal.set(Calendar.MILLISECOND, millisFromNanos(self.getNano()));
+        return cal;
+    }
+
+    /* ******** java.time.Period extension methods ******** */
+
+    /**
+     * Returns a {@link java.time.Period} that is {@code days} days longer than this period.
+     * No normalization is performed.
+     *
+     * @param self a Period
+     * @param days the number of days to increase this Period by
+     * @return a Period
+     * @since 3.0
+     */
+    public static Period plus(final Period self, long days) {
+        return self.plusDays(days);
+    }
+
+    /**
+     * Returns a {@link java.time.Period} that is {@code days} days shorter than this period.
+     * No normalization is performed.
+     *
+     * @param self a Period
+     * @param days the number of days to decrease this Period by
+     * @return a Period
+     * @since 3.0
+     */
+    public static Period minus(final Period self, long days) {
+        return self.minusDays(days);
+    }
+
+    /**
+     * Returns a {@link java.time.Period} that is one day longer than this period.
+     * No normalization is performed.
+     *
+     * @param self a Period
+     * @return a Period one day longer in length
+     * @since 3.0
+     */
+    public static Period next(final Period self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.Period} that is one day shorter than this period.
+     * No normalization is performed.
+     *
+     * @param self a Period
+     * @return a Period one day shorter in length
+     * @since 3.0
+     */
+    public static Period previous(final Period self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Supports the unary minus operator; equivalent to calling the {@link java.time.Period#negated()} method.
+     *
+     * @param self a Period
+     * @return a negated Period
+     * @since 3.0
+     */
+    public static Period negative(final Period self) {
+        return self.negated();
+    }
+
+    /**
+     * Supports the unary plus operator; returns a {@link java.time.Period} with all unit values positive.
+     * For example, a period of "2 years, -3 months, and -4 days" would result in a period of
+     * "2 years, 3 months, and 4 days." No normalization is performed.
+     *
+     * @param self a Period
+     * @return a positive Period
+     * @since 3.0
+     */
+    public static Period positive(final Period self) {
+        return !self.isNegative() ? self : self.withDays(Math.abs(self.getDays()))
+                .withMonths(Math.abs(self.getMonths()))
+                .withYears(Math.abs(self.getYears()));
+    }
+
+    /**
+     * Supports the multiply operator; equivalent to calling the {@link java.time.Period#multipliedBy(int)} method.
+     *
+     * @param self   a Period
+     * @param scalar a scalar to multiply each unit by
+     * @return a Period
+     * @since 3.0
+     */
+    public static Period multiply(final Period self, int scalar) {
+        return self.multipliedBy(scalar);
+    }
+
+    /* ******** java.time.Year extension methods ******** */
+
+    /**
+     * Returns a {@link java.time.Year} that is {@code years} years after this year.
+     *
+     * @param self  a Year
+     * @param years the number of years to add
+     * @return a Year
+     * @since 3.0
+     */
+    public static Year plus(final Year self, long years) {
+        return self.plusYears(years);
+    }
+
+    /**
+     * Returns a {@link java.time.Year} that is {@code years} years before this year.
+     *
+     * @param self  a Year
+     * @param years the number of years to subtract
+     * @return a Year
+     * @since 3.0
+     */
+    public static Year minus(final Year self, long years) {
+        return self.minusYears(years);
+    }
+
+    /**
+     * Returns a {@link java.time.Year} after this year.
+     *
+     * @param self  a Year
+     * @return the next Year
+     * @since 3.0
+     */
+    public static Year next(final Year self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.Year} before this year.
+     *
+     * @param self a Year
+     * @return the previous Year
+     * @since 3.0
+     */
+    public static Year previous(final Year self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.Period} between the first day of this year (inclusive) and the first day of the
+     * provided {@link java.time.Year} (exclusive).
+     *
+     * @param self a Year
+     * @param year another Year
+     * @return a Period between the Years
+     * @since 3.0
+     */
+    public static Period rightShift(final Year self, Year year) {
+        return Period.between(self.atDay(1), year.atDay(1));
+    }
+
+    /**
+     * Returns a {@link java.time.YearMonth} of this year and the provided {@link java.time.Month}.
+     *
+     * @param self  a Year
+     * @param month a Month
+     * @return a YearMonth
+     * @since 3.0
+     */
+    public static YearMonth leftShift(final Year self, Month month) {
+        return self.atMonth(month);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDate} of this year on the given {@link java.time.MonthDay}.
+     *
+     * @param self     a Year
+     * @param monthDay a MonthDay
+     * @return a LocalDate
+     * @since 3.0
+     */
+    public static LocalDate leftShift(final Year self, MonthDay monthDay) {
+        return self.atMonthDay(monthDay);
+    }
+
+    /**
+     * Equivalent to calling the {@link java.time.Year#get(java.time.temporal.TemporalField)} method with a
+     * {@link java.time.temporal.ChronoField#ERA} argument.
+     *
+     * Returns the era of the year, which is currently either 0 (BC) or 1 (AD).
+     *
+     * @param self a Year
+     * @return an int representing the era
+     * @since 3.0
+     */
+    public static int getEra(final Year self) {
+        return self.get(ChronoField.ERA);
+    }
+
+    /**
+     * Equivalent to calling the {@link java.time.Year#get(java.time.temporal.TemporalField)} method with a
+     * {@link java.time.temporal.ChronoField#YEAR_OF_ERA} argument.
+     *
+     * Since Year=0 represents 1 BC, the yearOfEra value of Year=0 is 1, Year=-1 is 2, and so on.
+     *
+     * @param self a Year
+     * @return the year value of the era
+     * @since 3.0
+     */
+    public static int getYearOfEra(final Year self) {
+        return self.get(ChronoField.YEAR_OF_ERA);
+    }
+
+    /* ******** java.time.YearMonth extension methods ******** */
+
+    /**
+     * Returns a {@link java.time.YearMonth} that is {@code months} months after this year/month.
+     *
+     * @param self   a YearMonth
+     * @param months the number of months to add
+     * @return a Year
+     * @since 3.0
+     */
+    public static YearMonth plus(final YearMonth self, long months) {
+        return self.plusMonths(months);
+    }
+
+    /**
+     * Returns a {@link java.time.YearMonth} that is {@code months} months before this year/month.
+     *
+     * @param self   a YearMonth
+     * @param months the number of months to subtract
+     * @return a Year
+     * @since 3.0
+     */
+    public static YearMonth minus(final YearMonth self, long months) {
+        return self.minusMonths(months);
+    }
+
+    /**
+     * Returns a {@link java.time.YearMonth} that is the month after this year/month.
+     *
+     * @param self  a YearMonth
+     * @return the next YearMonth
+     * @since 3.0
+     */
+    public static YearMonth next(final YearMonth self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.YearMonth} that is the month before this year/month.
+     *
+     * @param self a YearMonth
+     * @return the previous YearMonth
+     * @since 3.0
+     */
+    public static YearMonth previous(final YearMonth self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDate} of this year/month and the given day of the month.
+     *
+     * @param self       a YearMonth
+     * @param dayOfMonth a day of the month
+     * @return a LocalDate
+     * @since 3.0
+     */
+    public static LocalDate leftShift(final YearMonth self, int dayOfMonth) {
+        return self.atDay(dayOfMonth);
+    }
+
+    /**
+     * Returns a {@link java.time.Period} of time between the first day of this year/month (inclusive) and the
+     * given {@link java.time.YearMonth} (exclusive).
+     *
+     * @param self  a YearMonth
+     * @param other another YearMonth
+     * @return a Period
+     * @since 3.0
+     */
+    public static Period rightShift(YearMonth self, YearMonth other) {
+        return Period.between(self.atDay(1), other.atDay(1));
+    }
+
+    /* ******** java.time.ZonedDateTime extension methods ******** */
+
+    /**
+     * Formats this date/time with the provided {@link java.time.format.DateTimeFormatter} pattern.
+     *
+     * @param self    a ZonedDateTime
+     * @param pattern the formatting pattern
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String format(final ZonedDateTime self, String pattern) {
+        return self.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Formats this date/time in the provided, localized {@link java.time.format.FormatStyle}.
+     *
+     * @param self          a ZonedDateTime
+     * @param dateTimeStyle the FormatStyle
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String format(final ZonedDateTime self, FormatStyle dateTimeStyle) {
+        return self.format(DateTimeFormatter.ofLocalizedDateTime(dateTimeStyle));
+    }
+
+    /**
+     * Formats this date/time in the locale-specific {@link java.time.format.FormatStyle#SHORT} format style.
+     *
+     * @param self a ZonedDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String getDateTimeString(final ZonedDateTime self) {
+        return format(self, FormatStyle.SHORT);
+    }
+
+    /**
+     * Formats the date portion of this date/time in the locale-specific
+     * {@link java.time.format.FormatStyle#SHORT} format style.
+     *
+     * @param self a ZonedDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String getDateString(final ZonedDateTime self) {
+        return getDateString(self.toLocalDate());
+    }
+
+    /**
+     * Formats the time portion of this date/time in the locale-specific
+     * {@link java.time.format.FormatStyle#SHORT} format style.
+     *
+     * @param self a ZonedDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 3.0
+     */
+    public static String getTimeString(final ZonedDateTime self) {
+        return getTimeString(self.toLocalTime());
+    }
+
+    /**
+     * Returns an {@link java.time.ZonedDateTime} with the time portion cleared.
+     *
+     * @param self a ZonedDateTime
+     * @return a ZonedDateTime
+     * @since 3.0
+     */
+    public static ZonedDateTime clearTime(final ZonedDateTime self) {
+        return self.truncatedTo(ChronoUnit.DAYS);
+    }
+
+    /**
+     * Returns a {@link java.time.ZonedDateTime} that is {@code seconds} seconds after this date/time.
+     *
+     * @param self    an ZonedDateTime
+     * @param seconds the number of seconds to add
+     * @return a ZonedDateTime
+     * @since 3.0
+     */
+    public static ZonedDateTime plus(final ZonedDateTime self, long seconds) {
+        return self.plusSeconds(seconds);
+    }
+
+    /**
+     * Returns a {@link java.time.ZonedDateTime} that is {@code seconds} seconds before this date/time.
+     *
+     * @param self    a ZonedDateTime
+     * @param seconds the number of seconds to subtract
+     * @return a ZonedDateTime
+     * @since 3.0
+     */
+    public static ZonedDateTime minus(final ZonedDateTime self, long seconds) {
+        return self.minusSeconds(seconds);
+    }
+
+    /**
+     * Returns a {@link java.time.ZonedDateTime} that is one second after this date/time.
+     *
+     * @param self a ZonedDateTime
+     * @return a ZonedDateTime
+     * @since 3.0
+     */
+    public static ZonedDateTime next(final ZonedDateTime self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.ZonedDateTime} that is one second before this date/time.
+     *
+     * @param self a ZonedDateTime
+     * @return a ZonedDateTime
+     * @since 3.0
+     */
+    public static ZonedDateTime previous(final ZonedDateTime self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Date}.
+     * The time value of the returned date is truncated to milliseconds and will be
+     * adjusted to the current system default time zone.
+     *
+     * @param self a ZonedDateTime
+     * @return a java.util.Date
+     * @since 3.0
+     */
+    public static Date toDate(final ZonedDateTime self) {
+        return toCalendar(self).getTime();
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Calendar}.
+     * The time value of the returned calendar is truncated to milliseconds and the time zone
+     * is determined by the zone of this date/time.
+     *
+     * @param self an ZonedDateTime
+     * @return a java.util.Calendar
+     * @since 3.0
+     */
+    public static Calendar toCalendar(final ZonedDateTime self) {
+        Calendar cal = Calendar.getInstance(toTimeZone(self.getZone()));
+        cal.set(Calendar.DATE, self.getDayOfMonth());
+        cal.set(Calendar.MONTH, self.getMonthValue() - 1);
+        cal.set(Calendar.YEAR, self.getYear());
+        cal.set(Calendar.HOUR_OF_DAY, self.getHour());
+        cal.set(Calendar.MINUTE, self.getMinute());
+        cal.set(Calendar.SECOND, self.getSecond());
+        cal.set(Calendar.MILLISECOND, millisFromNanos(self.getNano()));
+        return cal;
+    }
+
+    /* ******** java.time.ZoneId extension methods ******** */
+
+    /**
+     * Returns a {@link java.util.TimeZone} equivalent to this zone.
+     *
+     * @param self a ZoneId
+     * @return a TimeZone
+     * @since 3.0
+     */
+    public static TimeZone toTimeZone(final ZoneId self) {
+        return TimeZone.getTimeZone(self);
+    }
+
+    /**
+     * Returns the name of this zone formatted according to the {@link java.time.format.TextStyle#FULL} text style.
+     *
+     * @param self a ZoneId
+     * @return the full display name of the ZoneId
+     * @since 3.0
+     */
+    public static String getFullName(final ZoneId self) {
+        return getFullName(self, Locale.getDefault());
+    }
+
+    /**
+     * Returns the name of this zone formatted according to the {@link java.time.format.TextStyle#FULL} text style
+     * for the provided {@link java.util.Locale}.
+     *
+     * @param self   a ZoneId
+     * @param locale a Locale
+     * @return the full display name of the ZoneId
+     * @since 3.0
+     */
+    public static String getFullName(final ZoneId self, Locale locale) {
+        return self.getDisplayName(TextStyle.FULL, locale);
+    }
+
+    /**
+     * Returns the name of this zone formatted according to the {@link java.time.format.TextStyle#SHORT} text style.
+     *
+     * @param self   a ZoneId
+     * @return the short display name of the ZoneId
+     * @since 3.0
+     */
+    public static String getShortName(final ZoneId self) {
+        return getShortName(self, Locale.getDefault());
+    }
+
+    /**
+     * Returns the name of this zone formatted according to the {@link java.time.format.TextStyle#SHORT} text style
+     * for the provided {@link java.util.Locale}.
+     *
+     * @param self   a ZoneId
+     * @param locale a Locale
+     * @return the short display name of the ZoneId
+     * @since 3.0
+     */
+    public static String getShortName(final ZoneId self, Locale locale) {
+        return self.getDisplayName(TextStyle.SHORT, locale);
+    }
+
+    /**
+     * Returns a {@link java.time.ZoneOffset} for this zone as of now.
+     *
+     * @param self a ZoneId
+     * @return a ZoneOffset
+     * @since 3.0
+     */
+    public static ZoneOffset getOffset(final ZoneId self) {
+        return getOffset(self, Instant.now());
+    }
+
+    /**
+     * Returns a {@link java.time.ZoneOffset} for this zone as of the provided {@link java.time.Instant}.
+     *
+     * @param self    a ZoneId
+     * @param instant an Instant
+     * @return a ZoneOffset
+     * @since 3.0
+     */
+    public static ZoneOffset getOffset(final ZoneId self, Instant instant) {
+        return self.getRules().getOffset(instant);
+    }
+
+    /**
+     * Returns a {@link java.time.ZonedDateTime} of this zone and the given {@link java.time.LocalDateTime}.
+     *
+     * @param self a ZoneId
+     * @return a ZonedDateTime
+     * @since 3.0
+     */
+    public static ZonedDateTime leftShift(final ZoneId self, LocalDateTime dateTime) {
+        return ZonedDateTime.of(dateTime, self);
+    }
+
+    /* ******** java.time.ZoneOffset extension methods ******** */
+
+    /**
+     * Returns a generally  equivalent {@link java.util.TimeZone}. The offset will be truncated to minutes.
+     *
+     * @param self a ZoneOffset
+     * @return a TimeZone
+     * @since 3.0
+     */
+    public static TimeZone toTimeZone(final ZoneOffset self) {
+        if (ZoneOffset.UTC.equals(self)) {
+            return TimeZone.getTimeZone("GMT");
+        } else if (getSeconds(self) == 0) {
+            return TimeZone.getTimeZone("GMT" + self.getId());
+        } else {
+            // TimeZone is only hours and minutes--no seconds
+            ZoneOffset noSeconds = ZoneOffset.ofHoursMinutes(getHours(self), getMinutes(self));
+            return TimeZone.getTimeZone("GMT" + noSeconds.getId());
+        }
+    }
+
+    /**
+     * Returns the value of the provided field for the ZoneOffset as if the ZoneOffset's
+     * hours/minutes/seconds were reckoned as a LocalTime.
+     */
+    private static int offsetFieldValue(ZoneOffset offset, TemporalField field) {
+        int offsetSeconds = offset.getTotalSeconds();
+        int value = LocalTime.ofSecondOfDay(Math.abs(offsetSeconds)).get(field);
+        return offsetSeconds < 0 ? value * -1 : value;
+    }
+
+    /**
+     * Returns the hours component of this offset. If the offset's total seconds are negative, a negative
+     * value will be returned.
+     *
+     * @param self a ZoneOffset
+     * @return the hours component value
+     * @since 3.0
+     */
+    public static int getHours(final ZoneOffset self) {
+        return offsetFieldValue(self, ChronoField.HOUR_OF_DAY);
+    }
+
+    /**
+     * Returns the minutes component of this offset. If the offset's total seconds are negative, a negative
+     * value will be returned.
+     *
+     * @param self a ZoneOffset
+     * @return the minutes component value
+     * @since 3.0
+     */
+    public static int getMinutes(final ZoneOffset self) {
+        return offsetFieldValue(self, ChronoField.MINUTE_OF_HOUR);
+    }
+
+    /**
+     * Returns the seconds component of this offset. This is not the same as the total seconds. For example:
+     * <pre>
+     *     def offset = ZoneOffset.ofHoursMinutesSeconds(0, 1, 1)
+     *     assert offset.seconds == 1
+     *     assert offset.totalSeconds == 61
+     * </pre>
+     * <p>
+     * If the offset's total seconds are negative, a negative value will be returned.
+     *
+     * @param self a ZoneOffset
+     * @return the seconds component value
+     * @since 3.0
+     */
+    public static int getSeconds(final ZoneOffset self) {
+        return offsetFieldValue(self, ChronoField.SECOND_OF_MINUTE);
+    }
+
+    /**
+     * Supports the getAt operator; equivalent to calling the
+     * {@link java.time.ZoneOffset#getLong(java.time.temporal.TemporalField)} method.
+     *
+     * @param self  a ZoneOffset
+     * @param field a TemporalField
+     * @return the ZoneOffset's field value
+     * @since 3.0
+     */
+    public static long getAt(final ZoneOffset self, TemporalField field) {
+        return self.getLong(field);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} of this offset and the provided {@link java.time.LocalDateTime}.
+     *
+     * @param self     a ZoneOffset
+     * @param dateTime a LocalDateTime
+     * @return an OffsetDateTime
+     * @since 3.0
+     */
+    public static OffsetDateTime leftShift(final ZoneOffset self, LocalDateTime dateTime) {
+        return OffsetDateTime.of(dateTime, self);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} of this offset and the provided {@link java.time.LocalTime}.
+     *
+     * @param self a ZoneOffset
+     * @param time a LocalTime
+     * @return an OffsetTime
+     * @since 3.0
+     */
+    public static OffsetTime leftShift(final ZoneOffset self, LocalTime time) {
+        return OffsetTime.of(time, self);
+    }
+
+    /* ******** java.time.DayOfWeek extension methods ******** */
+
+    /**
+     * Returns the {@link java.time.DayOfWeek} that is {@code days} many days after this day of the week.
+     *
+     * @param self a DayOfWeek
+     * @param days the number of days to move forward
+     * @return the DayOfWeek
+     * @since 3.0
+     */
+    public static DayOfWeek plus(final DayOfWeek self, int days) {
+        int daysPerWeek = DayOfWeek.values().length;
+        int val = ((self.getValue() + days - 1) % daysPerWeek) + 1;
+        return DayOfWeek.of(val > 0 ? val : daysPerWeek + val);
+    }
+
+    /**
+     * Returns the {@link java.time.DayOfWeek} that is {@code days} many days before this day of the week.
+     *
+     * @param self a DayOfWeek
+     * @param days the number of days to move back
+     * @return the DayOfWeek
+     * @since 3.0
+     */
+    public static DayOfWeek minus(final DayOfWeek self, int days) {
+        return plus(self, days * -1);
+    }
+
+    /**
+     * Returns {@code true} if this day of the week is a weekend day (Saturday or Sunday).
+     *
+     * @param self a DayOfWeek
+     * @return true if this DayOfWeek is Saturday or Sunday
+     * @since 3.0
+     */
+    public static boolean isWeekend(final DayOfWeek self) {
+        return self == DayOfWeek.SATURDAY || self == DayOfWeek.SUNDAY;
+    }
+
+    /**
+     * Returns {@code true} if the DayOfWeek is a weekday.
+     *
+     * @return true if this DayOfWeek is Monday through Friday
+     * @since 3.0
+     */
+    public static boolean isWeekday(final DayOfWeek self) {
+        return !isWeekend(self);
+    }
+
+    /* ******** java.time.Month extension methods ******** */
+
+    /**
+     * Returns the {@link java.time.Month} that is {@code months} months after this month.
+     *
+     * @param self   a Month
+     * @param months the number of months move forward
+     * @return the Month
+     * @since 3.0
+     */
+    public static Month plus(final Month self, int months) {
+        int monthsPerYear = Month.values().length;
+        int val = ((self.getValue() + months - 1) % monthsPerYear) + 1;
+        return Month.of(val > 0 ? val : monthsPerYear + val);
+    }
+
+    /**
+     * Returns the {@link java.time.Month} that is {@code months} months before this month.
+     *
+     * @param self   a Month
+     * @param months the number of months to move back
+     * @return the Month
+     * @since 3.0
+     */
+    public static Month minus(final Month self, int months) {
+        return plus(self, months * -1);
+    }
+
+    /**
+     * Creates a {@link java.time.MonthDay} at the provided day of the month.
+     *
+     * @param self       a Month
+     * @param dayOfMonth a day of the month
+     * @return a MonthDay
+     * @since 3.0
+     */
+    public static MonthDay leftShift(final Month self, int dayOfMonth) {
+        return MonthDay.of(self, dayOfMonth);
+    }
+
+    /**
+     * Creates a {@link java.time.YearMonth} at the provided {@link java.time.Year}.
+     *
+     * @param self a Month
+     * @param year a Year
+     * @return a YearMonth
+     * @since 3.0
+     */
+    public static YearMonth leftShift(final Month self, Year year) {
+        return YearMonth.of(year.getValue(), self);
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/99db9bf8/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index d8b7950..1a356a5 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -221,6 +221,7 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
     public static final Class[] DGM_LIKE_CLASSES = new Class[]{
             DefaultGroovyMethods.class,
             DateGroovyMethods.class,
+            DateTimeGroovyMethods.class,
             EncodingGroovyMethods.class,
             IOGroovyMethods.class,
             ProcessGroovyMethods.class,


[02/14] groovy git commit: use ISO for formatting methods; restrict upto/downto args to same type

Posted by pa...@apache.org.
use ISO for formatting methods; restrict upto/downto args to same type


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/b3bd8864
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/b3bd8864
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/b3bd8864

Branch: refs/heads/GROOVY_2_6_X
Commit: b3bd886471fe2bfe9dc5897cf9fcf7c3067413eb
Parents: 92378be
Author: Joe Wolf <jo...@gmail.com>
Authored: Sat Mar 17 20:49:45 2018 -0400
Committer: paulk <pa...@asert.com.au>
Committed: Thu Mar 22 00:41:46 2018 +1000

----------------------------------------------------------------------
 .../groovy/runtime/DateTimeGroovyMethods.java   | 133 +++++++++++--------
 src/test/groovy/DateTimeTest.groovy             |  28 ++++
 2 files changed, 106 insertions(+), 55 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/b3bd8864/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
index 97d7df7..529cbd0 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
@@ -46,6 +46,8 @@ public class DateTimeGroovyMethods {
     private DateTimeGroovyMethods() {
     }
 
+    private static final DateTimeFormatter ZONE_SHORT_FORMATTER = DateTimeFormatter.ofPattern("z");
+
     /**
      * For any Temporal subtype that does not use {@link java.time.temporal.ChronoUnit#SECONDS} as the unit for
      * the upto/downto methods, should have an entry.
@@ -96,6 +98,7 @@ public class DateTimeGroovyMethods {
      * @param to the ending Temporal
      * @param closure the zero or one-argument closure to call
      * @throws GroovyRuntimeException if this value is later than {@code to}
+     * @throws GroovyRuntimeException if {@code to} is a different type than this
      * @since 3.0
      */
     public static void upto(Temporal from, Temporal to, Closure closure) {
@@ -109,13 +112,15 @@ public class DateTimeGroovyMethods {
      *
      * If the unit is too large to iterate to the second Temporal exactly, such as iterating from two LocalDateTimes
      * that are seconds apart using {@java.time.temporal.ChronoUnit#DAYS} as the unit, the iteration will cease
-     * as soon as the current value of the iteration is later than the second Temporal argument.
+     * as soon as the current value of the iteration is later than the second Temporal argument. The closure will
+     * not be called with any value later than the {@code to} value.
      *
      * @param from the starting Temporal
      * @param to   the ending Temporal
      * @param unit the TemporalUnit to increment by
      * @param closure the zero or one-argument closure to call
      * @throws GroovyRuntimeException if this value is later than {@code to}
+     * @throws GroovyRuntimeException if {@code to} is a different type than this
      * @since 3.0
      */
     public static void upto(Temporal from, Temporal to, TemporalUnit unit, Closure closure) {
@@ -133,15 +138,14 @@ public class DateTimeGroovyMethods {
      * Returns true if the {@code from} can be iterated up to {@code to}.
      */
     private static boolean isUptoEligible(Temporal from, Temporal to) {
-        switch ((ChronoUnit) defaultUnitFor(from)) {
-            case YEARS:
-                return isNonnegative(DefaultGroovyStaticMethods.between(null, (Year) from, (Year) to));
-            case MONTHS:
-                return isNonnegative(DefaultGroovyStaticMethods.between(null, (YearMonth) from, (YearMonth) to));
-            case DAYS:
-                return isNonnegative(ChronoPeriod.between((ChronoLocalDate) from, (ChronoLocalDate) to));
-            default:
-                return isNonnegative(Duration.between(from, to));
+        TemporalAmount amount = rightShift(from, to);
+        if (amount instanceof Period) {
+            return isNonnegative((Period) amount);
+        } else if (amount instanceof Duration) {
+            return isNonnegative((Duration) amount);
+        } else {
+            throw new GroovyRuntimeException("Temporal implementations of "
+                    + from.getClass().getCanonicalName() + " are not supported by upto().");
         }
     }
 
@@ -162,6 +166,7 @@ public class DateTimeGroovyMethods {
      * @param to the ending Temporal
      * @param closure the zero or one-argument closure to call
      * @throws GroovyRuntimeException if this value is earlier than {@code to}
+     * @throws GroovyRuntimeException if {@code to} is a different type than this
      * @since 3.0
      */
     public static void downto(Temporal from, Temporal to, Closure closure) {
@@ -175,13 +180,15 @@ public class DateTimeGroovyMethods {
      *
      * If the unit is too large to iterate to the second Temporal exactly, such as iterating from two LocalDateTimes
      * that are seconds apart using {@java.time.temporal.ChronoUnit#DAYS} as the unit, the iteration will cease
-     * as soon as the current value of the iteration is earlier than the second Temporal argument.
+     * as soon as the current value of the iteration is earlier than the second Temporal argument. The closure will
+     * not be called with any value earlier than the {@code to} value.
      *
      * @param from the starting Temporal
      * @param to   the ending Temporal
      * @param unit the TemporalUnit to increment by
      * @param closure the zero or one-argument closure to call
      * @throws GroovyRuntimeException if this value is earlier than {@code to}
+     * @throws GroovyRuntimeException if {@code to} is a different type than this
      * @since 3.0
      */
     public static void downto(Temporal from, Temporal to, TemporalUnit unit, Closure closure) {
@@ -199,28 +206,47 @@ public class DateTimeGroovyMethods {
      * Returns true if the {@code from} can be iterated down to {@code to}.
      */
     private static boolean isDowntoEligible(Temporal from, Temporal to) {
-        switch ((ChronoUnit) defaultUnitFor(from)) {
-            case YEARS:
-                return isNonpositive(DefaultGroovyStaticMethods.between(null, (Year) from, (Year) to));
-            case MONTHS:
-                return isNonpositive(DefaultGroovyStaticMethods.between(null, (YearMonth) from, (YearMonth) to));
-            case DAYS:
-                return isNonpositive(ChronoPeriod.between((ChronoLocalDate) from, (ChronoLocalDate) to));
-            default:
-                return isNonpositive(Duration.between(from, to));
+        TemporalAmount amount = rightShift(from, to);
+        if (amount instanceof Period) {
+            return isNonpositive((Period) amount);
+        } else if (amount instanceof Duration) {
+            return isNonpositive((Duration) amount);
+        } else {
+            throw new GroovyRuntimeException("Temporal implementations of "
+                    + from.getClass().getCanonicalName() + " are not supported by downto().");
         }
     }
 
     /**
-     * Returns a {@link java.time.Duration} of time between this (inclusive) and {@code other} (exclusive).
+     * Returns a {@link java.time.Duration} or {@link java.time.Period} between this (inclusive) and the {@code other}
+     * {@link java.time.temporal.Temporal} (exclusive).
+     * <p>
+     * A Period will be returned for types {@link java.time.Year}, {@link java.time.YearMonth}, and
+     * {@link java.time.chrono.ChronoLocalDate}; otherwise, a Duration will be returned.
+     * <p>
+     * Note: if the Temporal is a ChronoLocalDate but not a {@link java.time.LocalDate}, a general
+     * {@link java.time.chrono.ChronoPeriod} will be returned as per the return type of the method
+     * {@link java.time.chrono.ChronoLocalDate#until(ChronoLocalDate)} .
      *
      * @param self  a Temporal
-     * @param other another Temporal
-     * @return an Duration between the two Instants
+     * @param other another Temporal of the same type
+     * @return an TemporalAmount between the two Temporals
      * @since 3.0
      */
-    public static Duration rightShift(final Temporal self, Temporal other) {
-        return Duration.between(self, other);
+    public static TemporalAmount rightShift(final Temporal self, Temporal other) {
+        if (!self.getClass().equals(other.getClass())) {
+            throw new GroovyRuntimeException("Temporal arguments must be of the same type.");
+        }
+        switch ((ChronoUnit) defaultUnitFor(self)) {
+            case YEARS:
+                return DefaultGroovyStaticMethods.between(null, (Year) self, (Year) other);
+            case MONTHS:
+                return DefaultGroovyStaticMethods.between(null, (YearMonth) self, (YearMonth) other);
+            case DAYS:
+                return ChronoPeriod.between((ChronoLocalDate) self, (ChronoLocalDate) other);
+            default:
+                return Duration.between(self, other);
+        }
     }
 
     /* ******** java.time.temporal.TemporalAccessor extension methods ******** */
@@ -488,7 +514,7 @@ public class DateTimeGroovyMethods {
     }
 
     /**
-     * Formats this date in the locale-specific {@link java.time.format.FormatStyle#SHORT} format style.
+     * Formats this date with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE} formatter.
      *
      * @param self a LocalDate
      * @return a formatted String
@@ -496,7 +522,7 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static String getDateString(final LocalDate self) {
-        return format(self, FormatStyle.SHORT);
+        return self.format(DateTimeFormatter.ISO_LOCAL_DATE);
     }
 
     /**
@@ -639,7 +665,7 @@ public class DateTimeGroovyMethods {
     }
 
     /**
-     * Formats this date/time in the locale-specific {@link java.time.format.FormatStyle#SHORT} format style.
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE_TIME} formatter.
      *
      * @param self a LocalDateTime
      * @return a formatted String
@@ -647,12 +673,11 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static String getDateTimeString(final LocalDateTime self) {
-        return format(self, FormatStyle.SHORT);
+        return self.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
     }
 
     /**
-     * Formats the date portion of this date/time in the locale-specific
-     * {@link java.time.format.FormatStyle#SHORT} format style.
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE} formatter.
      *
      * @param self a LocalDateTime
      * @return a formatted String
@@ -660,12 +685,11 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static String getDateString(final LocalDateTime self) {
-        return getDateString(self.toLocalDate());
+        return self.format(DateTimeFormatter.ISO_LOCAL_DATE);
     }
 
     /**
-     * Formats the time portion of this date/time in the locale-specific
-     * {@link java.time.format.FormatStyle#SHORT} format style.
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_TIME} formatter.
      *
      * @param self a LocalDateTime
      * @return a formatted String
@@ -673,7 +697,7 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static String getTimeString(final LocalDateTime self) {
-        return getTimeString(self.toLocalTime());
+        return self.format(DateTimeFormatter.ISO_LOCAL_TIME);
     }
 
     /**
@@ -819,7 +843,7 @@ public class DateTimeGroovyMethods {
     }
 
     /**
-     * Formats this time in the locale-specific {@link java.time.format.FormatStyle#SHORT} format style.
+     * Formats this time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_TIME} formatter.
      *
      * @param self a LocalTime
      * @return a formatted String
@@ -827,7 +851,7 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static String getTimeString(final LocalTime self) {
-        return format(self, FormatStyle.SHORT);
+        return self.format(DateTimeFormatter.ISO_LOCAL_TIME);
     }
 
     /**
@@ -985,7 +1009,7 @@ public class DateTimeGroovyMethods {
     }
 
     /**
-     * Formats this date/time in the locale-specific {@link java.time.format.FormatStyle#SHORT} format style.
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_OFFSET_DATE_TIME} formatter.
      *
      * @param self an OffsetDateTime
      * @return a formatted String
@@ -993,12 +1017,11 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static String getDateTimeString(final OffsetDateTime self) {
-        return format(self, FormatStyle.SHORT);
+        return self.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
     }
 
     /**
-     * Formats the date portion of this date/time in the locale-specific
-     * {@link java.time.format.FormatStyle#SHORT} format style.
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_OFFSET_DATE} formatter.
      *
      * @param self an OffsetDateTime
      * @return a formatted String
@@ -1006,12 +1029,11 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static String getDateString(final OffsetDateTime self) {
-        return getDateString(self.toLocalDate());
+        return self.format(DateTimeFormatter.ISO_OFFSET_DATE);
     }
 
     /**
-     * Formats the time portion of this date/time in the locale-specific
-     * {@link java.time.format.FormatStyle#SHORT} format style.
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_OFFSET_TIME} formatter.
      *
      * @param self an OffsetDateTime
      * @return a formatted String
@@ -1019,7 +1041,7 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static String getTimeString(final OffsetDateTime self) {
-        return getTimeString(self.toLocalTime());
+        return self.format(DateTimeFormatter.ISO_OFFSET_TIME);
     }
 
     /**
@@ -1134,7 +1156,7 @@ public class DateTimeGroovyMethods {
     }
 
     /**
-     * Formats this time in the locale-specific {@link java.time.format.FormatStyle#SHORT} format style.
+     * Formats this time with the {@link java.time.format.DateTimeFormatter#ISO_OFFSET_TIME} formatter.
      *
      * @param self an OffsetTime
      * @return a formatted String
@@ -1142,7 +1164,7 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static String getTimeString(final OffsetTime self) {
-        return format(self, FormatStyle.SHORT);
+        return self.format(DateTimeFormatter.ISO_OFFSET_TIME);
     }
 
     /**
@@ -1573,7 +1595,8 @@ public class DateTimeGroovyMethods {
     }
 
     /**
-     * Formats this date/time in the locale-specific {@link java.time.format.FormatStyle#SHORT} format style.
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE_TIME} formatter
+     * and appends the zone's short name, e.g. {@code 2018-03-10T14:34:55.144EST}.
      *
      * @param self a ZonedDateTime
      * @return a formatted String
@@ -1581,12 +1604,12 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static String getDateTimeString(final ZonedDateTime self) {
-        return format(self, FormatStyle.SHORT);
+        return self.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + self.format(ZONE_SHORT_FORMATTER);
     }
 
     /**
-     * Formats the date portion of this date/time in the locale-specific
-     * {@link java.time.format.FormatStyle#SHORT} format style.
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE} formatter
+     * and appends the zone's short name, e.g. {@code 2018-03-10EST}.
      *
      * @param self a ZonedDateTime
      * @return a formatted String
@@ -1594,12 +1617,12 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static String getDateString(final ZonedDateTime self) {
-        return getDateString(self.toLocalDate());
+        return self.format(DateTimeFormatter.ISO_LOCAL_DATE) + self.format(ZONE_SHORT_FORMATTER);
     }
 
     /**
-     * Formats the time portion of this date/time in the locale-specific
-     * {@link java.time.format.FormatStyle#SHORT} format style.
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_TIME} formatter
+     * and appends the zone's short name, e.g. {@code 14:34:55.144EST}.
      *
      * @param self a ZonedDateTime
      * @return a formatted String
@@ -1607,7 +1630,7 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static String getTimeString(final ZonedDateTime self) {
-        return getTimeString(self.toLocalTime());
+        return self.format(DateTimeFormatter.ISO_LOCAL_TIME) + self.format(ZONE_SHORT_FORMATTER);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/groovy/blob/b3bd8864/src/test/groovy/DateTimeTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/groovy/DateTimeTest.groovy b/src/test/groovy/DateTimeTest.groovy
index 46eae17..db2e266 100644
--- a/src/test/groovy/DateTimeTest.groovy
+++ b/src/test/groovy/DateTimeTest.groovy
@@ -2,6 +2,7 @@ package groovy
 
 import java.text.SimpleDateFormat
 import java.time.*
+import java.time.chrono.JapaneseDate
 import java.time.temporal.ChronoField
 import java.time.temporal.ChronoUnit
 
@@ -267,6 +268,33 @@ class DateTimeTest extends GroovyTestCase {
         assert yearMonthPeriod.months == 2
     }
 
+    void testRightShiftDifferentTypes() {
+        try {
+            LocalDate.now() >> LocalTime.now()
+            fail('Should not be able to use right shift on different Temporal types.')
+        } catch (e) {
+            assert e instanceof GroovyRuntimeException
+        }
+    }
+
+    void testUptoDifferentTypes() {
+        try {
+            LocalDate.now().upto(JapaneseDate.now().plus(1, ChronoUnit.MONTHS)) { d -> }
+            fail('Cannot use upto() with two different Temporal types.')
+        } catch (e) {
+            assert e instanceof GroovyRuntimeException
+        }
+    }
+
+    void testDowntoDifferentTypes() {
+        try {
+            LocalDate.now().downto(JapaneseDate.now().minus(1, ChronoUnit.MONTHS)) { d -> }
+            fail('Cannot use downto() with two different argument types.')
+        } catch (e) {
+            assert e instanceof GroovyRuntimeException
+        }
+    }
+
     void testUptoSelfWithDefaultUnit() {
         def epoch = Instant.ofEpochMilli(0)
 


[14/14] groovy git commit: adjust for JDK7 compatibility

Posted by pa...@apache.org.
adjust for JDK7 compatibility


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/55865ea9
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/55865ea9
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/55865ea9

Branch: refs/heads/GROOVY_2_6_X
Commit: 55865ea96fa52b30988a9cca33afd08250ed0186
Parents: 718a820
Author: paulk <pa...@asert.com.au>
Authored: Thu Mar 22 00:45:57 2018 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Thu Mar 22 02:01:41 2018 +1000

----------------------------------------------------------------------
 settings.gradle                                         |  9 ++++++---
 .../groovy/runtime/DefaultGroovyStaticMethods.java      |  8 ++++----
 subprojects/groovy-datetime/build.gradle                |  5 +++++
 .../groovy/datetime/extensions/DateTimeExtensions.java  | 12 ++++++------
 4 files changed, 21 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/55865ea9/settings.gradle
----------------------------------------------------------------------
diff --git a/settings.gradle b/settings.gradle
index 180ce83..9ad6235 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -19,7 +19,6 @@
 def subprojects = ['groovy-ant',
         'groovy-bsf',
         'groovy-console',
-        'groovy-datetime',
         'groovy-dateutil',
         'groovy-docgenerator',
         'groovy-groovydoc',
@@ -37,10 +36,14 @@ def subprojects = ['groovy-ant',
         'tests-vm8',
         'groovy-testng',
         'groovy-xml',
-        'groovy-macro',
-        'performance'
+        'groovy-macro'
 ]
 
+if (JavaVersion.current().isJava8Compatible()) {
+    subprojects << 'groovy-datetime'
+    subprojects << 'performance'
+}
+
 if (hasProperty('stressTests')) {
     subprojects << 'stress'
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/55865ea9/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
index 421649f..04421af 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
@@ -26,11 +26,11 @@ import java.io.File;
 import java.io.IOException;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
-import java.time.*;
-import java.time.format.DateTimeFormatter;
-import java.util.*;
+import java.util.Date;
+import java.util.Locale;
+import java.util.ResourceBundle;
+import java.util.TimeZone;
 import java.util.regex.Matcher;
-import java.util.stream.Collectors;
 
 /**
  * This class defines all the new static groovy methods which appear on normal

http://git-wip-us.apache.org/repos/asf/groovy/blob/55865ea9/subprojects/groovy-datetime/build.gradle
----------------------------------------------------------------------
diff --git a/subprojects/groovy-datetime/build.gradle b/subprojects/groovy-datetime/build.gradle
index 92326c7..52bdf47 100644
--- a/subprojects/groovy-datetime/build.gradle
+++ b/subprojects/groovy-datetime/build.gradle
@@ -22,6 +22,11 @@ dependencies {
     testCompile project(':groovy-dateutil')
 }
 
+tasks.withType(JavaCompile) {
+    sourceCompatibility = 1.8
+    targetCompatibility = 1.8
+}
+
 task moduleDescriptor(type: org.codehaus.groovy.gradle.WriteExtensionDescriptorTask) {
     extensionClasses = 'org.apache.groovy.datetime.extensions.DateTimeExtensions'
     staticExtensionClasses = 'org.apache.groovy.datetime.extensions.DateTimeStaticExtensions'

http://git-wip-us.apache.org/repos/asf/groovy/blob/55865ea9/subprojects/groovy-datetime/src/main/java/org/apache/groovy/datetime/extensions/DateTimeExtensions.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-datetime/src/main/java/org/apache/groovy/datetime/extensions/DateTimeExtensions.java b/subprojects/groovy-datetime/src/main/java/org/apache/groovy/datetime/extensions/DateTimeExtensions.java
index 9ba6014..28ec8e1 100644
--- a/subprojects/groovy-datetime/src/main/java/org/apache/groovy/datetime/extensions/DateTimeExtensions.java
+++ b/subprojects/groovy-datetime/src/main/java/org/apache/groovy/datetime/extensions/DateTimeExtensions.java
@@ -93,12 +93,12 @@ public class DateTimeExtensions {
      * what the unit should be for this number.
      */
     private static TemporalUnit defaultUnitFor(Temporal temporal) {
-        return DEFAULT_UNITS.entrySet()
-                .stream()
-                .filter(e -> e.getKey().isAssignableFrom(temporal.getClass()))
-                .findFirst()
-                .map(Map.Entry::getValue)
-                .orElse(ChronoUnit.SECONDS);
+        for (Map.Entry<Class<? extends Temporal>, TemporalUnit> next : DEFAULT_UNITS.entrySet()) {
+            if (next.getKey().isAssignableFrom(temporal.getClass())) {
+                return next.getValue();
+            }
+        }
+        return ChronoUnit.SECONDS;
     }
 
     /**


[12/14] groovy git commit: move datetime extensions to their own module

Posted by pa...@apache.org.
http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
deleted file mode 100644
index 93cecf5..0000000
--- a/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
+++ /dev/null
@@ -1,2030 +0,0 @@
-/*
- *  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.
- */
-package org.codehaus.groovy.runtime;
-
-import groovy.lang.Closure;
-import groovy.lang.GroovyRuntimeException;
-
-import java.time.*;
-import java.time.chrono.ChronoLocalDate;
-import java.time.chrono.ChronoPeriod;
-import java.time.format.DateTimeFormatter;
-import java.time.format.FormatStyle;
-import java.time.format.TextStyle;
-import java.time.temporal.*;
-import java.util.*;
-
-import static java.time.temporal.ChronoUnit.DAYS;
-import static java.time.temporal.ChronoUnit.MONTHS;
-import static java.time.temporal.ChronoUnit.YEARS;
-
-/**
- * This class defines new Groovy methods which appear on normal JDK
- * Date/Time API (java.time) classes inside the Groovy environment.
- */
-public class DateTimeGroovyMethods {
-
-    // Static methods only
-    private DateTimeGroovyMethods() {
-    }
-
-    private static final DateTimeFormatter ZONE_SHORT_FORMATTER = DateTimeFormatter.ofPattern("z");
-
-    /**
-     * For any Temporal subtype that does not use {@link java.time.temporal.ChronoUnit#SECONDS} as the unit for
-     * the upto/downto methods, should have an entry.
-     */
-    private static Map<Class<? extends Temporal>, TemporalUnit> DEFAULT_UNITS = new HashMap<>();
-    static {
-        DEFAULT_UNITS.put(ChronoLocalDate.class, DAYS);
-        DEFAULT_UNITS.put(YearMonth.class, MONTHS);
-        DEFAULT_UNITS.put(Year.class, YEARS);
-    }
-
-    /**
-     * A number of extension methods permit a long or int to be provided as a parameter. This method determines
-     * what the unit should be for this number.
-     */
-    private static TemporalUnit defaultUnitFor(Temporal temporal) {
-        return DEFAULT_UNITS.entrySet()
-                .stream()
-                .filter(e -> e.getKey().isAssignableFrom(temporal.getClass()))
-                .findFirst()
-                .map(Map.Entry::getValue)
-                .orElse(ChronoUnit.SECONDS);
-    }
-
-    /**
-     * Truncates a nanosecond value to milliseconds. No rounding.
-     */
-    private static int millisFromNanos(int nanos) {
-        return nanos / 1_000_000;
-    }
-
-    /* ******** java.time.temporal.Temporal extension methods ******** */
-
-    /**
-     * Iterates from this to the {@code to} {@link java.time.temporal.Temporal}, inclusive, incrementing by one
-     * unit each iteration, calling the closure once per iteration. The closure may accept a single
-     * {@link java.time.temporal.Temporal} argument.
-     * <p>
-     * The particular unit incremented by depends on the specific sub-type of {@link java.time.temporal.Temporal}.
-     * Most sub-types use a unit of {@link java.time.temporal.ChronoUnit#SECONDS} except for
-     * <ul>
-     * <li>{@link java.time.chrono.ChronoLocalDate} and its sub-types use {@link java.time.temporal.ChronoUnit#DAYS}.
-     * <li>{@link java.time.YearMonth} uses {@link java.time.temporal.ChronoUnit#MONTHS}.
-     * <li>{@link java.time.Year} uses {@link java.time.temporal.ChronoUnit#YEARS}.
-     * </ul>
-     *
-     * @param from the starting Temporal
-     * @param to the ending Temporal
-     * @param closure the zero or one-argument closure to call
-     * @throws GroovyRuntimeException if this value is later than {@code to}
-     * @throws GroovyRuntimeException if {@code to} is a different type than this
-     * @since 3.0
-     */
-    public static void upto(Temporal from, Temporal to, Closure closure) {
-        upto(from, to, defaultUnitFor(from), closure);
-    }
-
-    /**
-     * Iterates from this to the {@code to} {@link java.time.temporal.Temporal}, inclusive, incrementing by one
-     * {@code unit} each iteration, calling the closure once per iteration. The closure may accept a single
-     * {@link java.time.temporal.Temporal} argument.
-     *
-     * If the unit is too large to iterate to the second Temporal exactly, such as iterating from two LocalDateTimes
-     * that are seconds apart using {@java.time.temporal.ChronoUnit#DAYS} as the unit, the iteration will cease
-     * as soon as the current value of the iteration is later than the second Temporal argument. The closure will
-     * not be called with any value later than the {@code to} value.
-     *
-     * @param from the starting Temporal
-     * @param to   the ending Temporal
-     * @param unit the TemporalUnit to increment by
-     * @param closure the zero or one-argument closure to call
-     * @throws GroovyRuntimeException if this value is later than {@code to}
-     * @throws GroovyRuntimeException if {@code to} is a different type than this
-     * @since 3.0
-     */
-    public static void upto(Temporal from, Temporal to, TemporalUnit unit, Closure closure) {
-        if (isUptoEligible(from, to)) {
-            for (Temporal i = from; isUptoEligible(i, to); i = i.plus(1, unit)) {
-                closure.call(i);
-            }
-        } else {
-            throw new GroovyRuntimeException("The argument (" + to +
-                    ") to upto() cannot be earlier than the value (" + from + ") it's called on.");
-        }
-    }
-
-    /**
-     * Returns true if the {@code from} can be iterated up to {@code to}.
-     */
-    private static boolean isUptoEligible(Temporal from, Temporal to) {
-        TemporalAmount amount = rightShift(from, to);
-        if (amount instanceof Period) {
-            return isNonnegative((Period) amount);
-        } else if (amount instanceof Duration) {
-            return isNonnegative((Duration) amount);
-        } else {
-            throw new GroovyRuntimeException("Temporal implementations of "
-                    + from.getClass().getCanonicalName() + " are not supported by upto().");
-        }
-    }
-
-    /**
-     * Iterates from this to the {@code to} {@link java.time.temporal.Temporal}, inclusive, decrementing by one
-     * unit each iteration, calling the closure once per iteration. The closure may accept a single
-     * {@link java.time.temporal.Temporal} argument.
-     * <p>
-     * The particular unit decremented by depends on the specific sub-type of {@link java.time.temporal.Temporal}.
-     * Most sub-types use a unit of {@link java.time.temporal.ChronoUnit#SECONDS} except for
-     * <ul>
-     * <li>{@link java.time.chrono.ChronoLocalDate} and its sub-types use {@link java.time.temporal.ChronoUnit#DAYS}.
-     * <li>{@link java.time.YearMonth} uses {@link java.time.temporal.ChronoUnit#MONTHS}.
-     * <li>{@link java.time.Year} uses {@link java.time.temporal.ChronoUnit#YEARS}.
-     * </ul>
-     *
-     * @param from the starting Temporal
-     * @param to the ending Temporal
-     * @param closure the zero or one-argument closure to call
-     * @throws GroovyRuntimeException if this value is earlier than {@code to}
-     * @throws GroovyRuntimeException if {@code to} is a different type than this
-     * @since 3.0
-     */
-    public static void downto(Temporal from, Temporal to, Closure closure) {
-        downto(from, to, defaultUnitFor(from), closure);
-    }
-
-    /**
-     * Iterates from this to the {@code to} {@link java.time.temporal.Temporal}, inclusive, decrementing by one
-     * {@code unit} each iteration, calling the closure once per iteration. The closure may accept a single
-     * {@link java.time.temporal.Temporal} argument.
-     *
-     * If the unit is too large to iterate to the second Temporal exactly, such as iterating from two LocalDateTimes
-     * that are seconds apart using {@java.time.temporal.ChronoUnit#DAYS} as the unit, the iteration will cease
-     * as soon as the current value of the iteration is earlier than the second Temporal argument. The closure will
-     * not be called with any value earlier than the {@code to} value.
-     *
-     * @param from the starting Temporal
-     * @param to   the ending Temporal
-     * @param unit the TemporalUnit to increment by
-     * @param closure the zero or one-argument closure to call
-     * @throws GroovyRuntimeException if this value is earlier than {@code to}
-     * @throws GroovyRuntimeException if {@code to} is a different type than this
-     * @since 3.0
-     */
-    public static void downto(Temporal from, Temporal to, TemporalUnit unit, Closure closure) {
-        if (isDowntoEligible(from, to)) {
-            for (Temporal i = from; isDowntoEligible(i, to); i = i.minus(1, unit)) {
-                closure.call(i);
-            }
-        } else {
-            throw new GroovyRuntimeException("The argument (" + to +
-                    ") to downto() cannot be later than the value (" + from + ") it's called on.");
-        }
-    }
-
-    /**
-     * Returns true if the {@code from} can be iterated down to {@code to}.
-     */
-    private static boolean isDowntoEligible(Temporal from, Temporal to) {
-        TemporalAmount amount = rightShift(from, to);
-        if (amount instanceof Period) {
-            return isNonpositive((Period) amount);
-        } else if (amount instanceof Duration) {
-            return isNonpositive((Duration) amount);
-        } else {
-            throw new GroovyRuntimeException("Temporal implementations of "
-                    + from.getClass().getCanonicalName() + " are not supported by downto().");
-        }
-    }
-
-    /**
-     * Returns a {@link java.time.Duration} or {@link java.time.Period} between this (inclusive) and the {@code other}
-     * {@link java.time.temporal.Temporal} (exclusive).
-     * <p>
-     * A Period will be returned for types {@link java.time.Year}, {@link java.time.YearMonth}, and
-     * {@link java.time.chrono.ChronoLocalDate}; otherwise, a Duration will be returned.
-     * <p>
-     * Note: if the Temporal is a ChronoLocalDate but not a {@link java.time.LocalDate}, a general
-     * {@link java.time.chrono.ChronoPeriod} will be returned as per the return type of the method
-     * {@link java.time.chrono.ChronoLocalDate#until(ChronoLocalDate)} .
-     *
-     * @param self  a Temporal
-     * @param other another Temporal of the same type
-     * @return an TemporalAmount between the two Temporals
-     * @since 3.0
-     */
-    public static TemporalAmount rightShift(final Temporal self, Temporal other) {
-        if (!self.getClass().equals(other.getClass())) {
-            throw new GroovyRuntimeException("Temporal arguments must be of the same type.");
-        }
-        switch ((ChronoUnit) defaultUnitFor(self)) {
-            case YEARS:
-                return DefaultGroovyStaticMethods.between(null, (Year) self, (Year) other);
-            case MONTHS:
-                return DefaultGroovyStaticMethods.between(null, (YearMonth) self, (YearMonth) other);
-            case DAYS:
-                return ChronoPeriod.between((ChronoLocalDate) self, (ChronoLocalDate) other);
-            default:
-                return Duration.between(self, other);
-        }
-    }
-
-    /* ******** java.time.temporal.TemporalAccessor extension methods ******** */
-
-    /**
-     * Supports the getAt operator; equivalent to calling the
-     * {@link java.time.temporal.TemporalAccessor#getLong(java.time.temporal.TemporalField)} method.
-     *
-     * @param self  a TemporalAccessor
-     * @param field a non-null TemporalField
-     * @return the value for the field
-     * @throws DateTimeException if a value for the field cannot be obtained
-     * @throws UnsupportedTemporalTypeException if the field is not supported
-     * @throws ArithmeticException if numeric overflow occurs
-     * @since 3.0
-     */
-    public static long getAt(final TemporalAccessor self, TemporalField field) {
-        return self.getLong(field);
-    }
-
-    /* ******** java.time.temporal.TemporalAmount extension methods ******** */
-
-    /**
-     * Supports the getAt operator; equivalent to calling the
-     * {@link java.time.temporal.TemporalAmount#get(TemporalUnit)} method.
-     *
-     * @param self  a TemporalAmount
-     * @param unit  a non-null TemporalUnit
-     * @return the value for the field
-     * @throws DateTimeException if a value for the field cannot be obtained
-     * @throws UnsupportedTemporalTypeException if the field is not supported
-     * @throws ArithmeticException if numeric overflow occurs
-     * @since 3.0
-     */
-    public static long getAt(final TemporalAmount self, TemporalUnit unit) {
-        return self.get(unit);
-    }
-
-    /* ******** java.time.Duration extension methods ******** */
-
-    /**
-     * Returns a {@link java.time.Duration} that is {@code seconds} seconds longer than this duration.
-     *
-     * @param self    a Duration
-     * @param seconds the number of seconds to add
-     * @return a Duration
-     * @since 3.0
-     */
-    public static Duration plus(final Duration self, long seconds) {
-        return self.plusSeconds(seconds);
-    }
-
-    /**
-     * Returns a {@link java.time.Duration} that is {@code seconds} seconds shorter that this duration.
-     *
-     * @param self    a Duration
-     * @param seconds the number of seconds to subtract
-     * @return a Duration
-     * @since 3.0
-     */
-    public static Duration minus(final Duration self, long seconds) {
-        return self.minusSeconds(seconds);
-    }
-
-    /**
-     * Returns a {@link java.time.Duration} that is one second longer than this duration.
-     *
-     * @param self a Duration
-     * @return a Duration
-     * @since 3.0
-     */
-    public static Duration next(final Duration self) {
-        return self.plusSeconds(1);
-    }
-
-    /**
-     * Returns a {@link java.time.Duration} that is one second shorter than this duration.
-     *
-     * @param self a Duration
-     * @return a Duration
-     * @since 3.0
-     */
-    public static Duration previous(final Duration self) {
-        return self.minusSeconds(1);
-    }
-
-    /**
-     * Supports the unary minus operator; equivalent to calling the {@link Duration#negated()} method.
-     *
-     * @param self a Duration
-     * @return a Duration
-     * @since 3.0
-     */
-    public static Duration negative(final Duration self) {
-        return self.negated();
-    }
-
-    /**
-     * Supports the unary plus operator; equivalent to calling the {@link Duration#abs()} method.
-     *
-     * @param self a Duration
-     * @return a Duration
-     * @since 3.0
-     */
-    public static Duration positive(final Duration self) {
-        return self.abs();
-    }
-
-    /**
-     * Supports the multiplication operator; equivalent to calling the {@link Duration#multipliedBy(long)} method.
-     *
-     * @param self   a Duration
-     * @param scalar the value to multiply by
-     * @return a Duration
-     * @since 3.0
-     */
-    public static Duration multiply(final Duration self, long scalar) {
-        return self.multipliedBy(scalar);
-    }
-
-    /**
-     * Supports the division operator; equivalent to calling the {@link Duration#dividedBy(long)} method.
-     *
-     * @param self   a Duration
-     * @param scalar the value to divide by
-     * @return a Duration
-     * @since 3.0
-     */
-    public static Duration div(final Duration self, long scalar) {
-        return self.dividedBy(scalar);
-    }
-
-    /**
-     * Returns true if this duration is positive, excluding zero.
-     *
-     * @param self a Duration
-     * @return true if positive
-     * @since 3.0
-     */
-    public static boolean isPositive(final Duration self) {
-        return !self.isZero() && !self.isNegative();
-    }
-
-    /**
-     * Returns true if this duration is zero or positive.
-     *
-     * @param self a Duration
-     * @return true if nonnegative
-     * @since 3.0
-     */
-    public static boolean isNonnegative(final Duration self) {
-        return self.isZero() || !self.isNegative();
-    }
-
-    /**
-     * Returns true if this duration is zero or negative.
-     *
-     * @param self a Duration
-     * @return true if nonpositive
-     * @since 3.0
-     */
-    public static boolean isNonpositive(final Duration self) {
-        return self.isZero() || self.isNegative();
-    }
-
-    /* ******** java.time.Instant extension methods ******** */
-
-    /**
-     * Returns an {@link java.time.Instant} that is {@code seconds} seconds after this instant.
-     *
-     * @param self    an Instant
-     * @param seconds the number of seconds to add
-     * @return an Instant
-     * @since 3.0
-     */
-    public static Instant plus(final Instant self, long seconds) {
-        return self.plusSeconds(seconds);
-    }
-
-    /**
-     * Returns an {@link java.time.Instant} that is {@code seconds} seconds before this instant.
-     *
-     * @param self    an Instant
-     * @param seconds the number of seconds to subtract
-     * @return an Instant
-     * @since 3.0
-     */
-    public static Instant minus(final Instant self, long seconds) {
-        return self.minusSeconds(seconds);
-    }
-
-    /**
-     * Returns an {@link java.time.Instant} that is one second after this instant.
-     *
-     * @param self an Instant
-     * @return an Instant one second ahead
-     * @since 3.0
-     */
-    public static Instant next(final Instant self) {
-        return plus(self, 1);
-    }
-
-    /**
-     * Returns an {@link java.time.Instant} that one second before this instant.
-     *
-     * @param self an Instant
-     * @return an Instant one second behind
-     * @since 3.0
-     */
-    public static Instant previous(final Instant self) {
-        return minus(self, 1);
-    }
-
-    /**
-     * Returns a generally equivalent {@link java.util.Date} according the number of milliseconds since the epoch,
-     * adjusted into the system default time zone.
-     *
-     * @param self an Instant
-     * @return a Date
-     * @since 3.0
-     */
-    public static Date toDate(final Instant self) {
-        return new Date(self.toEpochMilli());
-    }
-
-    /**
-     * Returns a generally equivalent {@link java.util.Calendar} in the GMT time zone, truncated to milliseconds.
-     *
-     * @param self an Instant
-     * @return a Calendar
-     * @since 3.0
-     */
-    public static Calendar toCalendar(final Instant self) {
-        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
-        cal.setTime(toDate(self));
-        return cal;
-    }
-
-    /* ******** java.time.LocalDate extension methods ******** */
-
-    /**
-     * Formats this date with the provided {@link java.time.format.DateTimeFormatter} pattern.
-     *
-     * @param self    a LocalDate
-     * @param pattern the formatting pattern
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String format(final LocalDate self, String pattern) {
-        return self.format(DateTimeFormatter.ofPattern(pattern));
-    }
-
-    /**
-     * Formats this date in the provided, localized {@link java.time.format.FormatStyle}.
-     *
-     * @param self      a LocalDate
-     * @param dateStyle the FormatStyle
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String format(final LocalDate self, FormatStyle dateStyle) {
-        return self.format(DateTimeFormatter.ofLocalizedDate(dateStyle));
-    }
-
-    /**
-     * Formats this date with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE} formatter.
-     *
-     * @param self a LocalDate
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String getDateString(final LocalDate self) {
-        return self.format(DateTimeFormatter.ISO_LOCAL_DATE);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalDate} that is {@code days} days after this date.
-     *
-     * @param self a LocalDate
-     * @param days the number of days to add
-     * @return a LocalDate
-     * @since 3.0
-     */
-    public static LocalDate plus(final LocalDate self, long days) {
-        return self.plusDays(days);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalDate} that is {@code days} days before this date.
-     *
-     * @param self a LocalDate
-     * @param days the number of days to subtract
-     * @return a LocalDate
-     * @since 3.0
-     */
-    public static LocalDate minus(final LocalDate self, long days) {
-        return self.minusDays(days);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalDate} one day after this date.
-     *
-     * @param self a LocalDate
-     * @return the next day
-     * @since 3.0
-     */
-    public static LocalDate next(final LocalDate self) {
-        return plus(self, 1);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalDate} one day before this date.
-     *
-     * @param self a LocalDate
-     * @return the previous day
-     * @since 3.0
-     */
-    public static LocalDate previous(final LocalDate self) {
-        return minus(self, 1);
-    }
-
-    /**
-     * Returns a {@link java.time.Period} equivalent to the time between this date (inclusive)
-     * and the provided {@link java.time.LocalDate} (exclusive).
-     *
-     * @param self  a LocalDate
-     * @param other another LocalDate
-     * @return a Period representing the time between the two LocalDates
-     * @since 3.0
-     */
-    public static Period rightShift(final LocalDate self, LocalDate other) {
-        return Period.between(self, other);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalDateTime} from this date and the provided {@link java.time.LocalTime}.
-     *
-     * @param self a LocalDate
-     * @param time a LocalTime
-     * @return a LocalDateTime
-     * @since 3.0
-     */
-    public static LocalDateTime leftShift(final LocalDate self, LocalTime time) {
-        return LocalDateTime.of(self, time);
-    }
-
-    /**
-     * Returns a {@link java.time.OffsetDateTime} from this date and the provided {@link java.time.OffsetTime}.
-     *
-     * @param self a LocalDate
-     * @param time an OffsetTime
-     * @return an OffsetDateTime
-     * @since 3.0
-     */
-    public static OffsetDateTime leftShift(final LocalDate self, OffsetTime time) {
-        return time.atDate(self);
-    }
-
-    /**
-     * Returns an equivalent instance of {@link java.util.Date}.
-     * The time portion of the returned date is cleared.
-     *
-     * @param self a LocalDate
-     * @return a java.util.Date
-     * @since 3.0
-     */
-    public static Date toDate(final LocalDate self) {
-        return toCalendar(self).getTime();
-    }
-
-    /**
-     * Returns an equivalent instance of {@link java.util.Calendar}.
-     * The time portion of the returned calendar is cleared and the time zone is the current system default.
-     *
-     * @param self a LocalDate
-     * @return a java.util.Calendar
-     * @since 3.0
-     */
-    public static Calendar toCalendar(final LocalDate self) {
-        Calendar cal = Calendar.getInstance();
-        cal.set(Calendar.DATE, self.getDayOfMonth());
-        cal.set(Calendar.MONTH, self.getMonthValue() - 1);
-        cal.set(Calendar.YEAR, self.getYear());
-        return DateGroovyMethods.clearTime(cal);
-    }
-
-    /* ******** java.time.LocalDateTime extension methods ******** */
-
-    /**
-     * Formats this date/time with the provided {@link java.time.format.DateTimeFormatter} pattern.
-     *
-     * @param self    a LocalDateTime
-     * @param pattern the formatting pattern
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String format(final LocalDateTime self, String pattern) {
-        return self.format(DateTimeFormatter.ofPattern(pattern));
-    }
-
-    /**
-     * Formats this date/time in the provided, localized {@link java.time.format.FormatStyle}.
-     *
-     * @param self          a LocalDateTime
-     * @param dateTimeStyle the FormatStyle
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String format(final LocalDateTime self, FormatStyle dateTimeStyle) {
-        return self.format(DateTimeFormatter.ofLocalizedDateTime(dateTimeStyle));
-    }
-
-    /**
-     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE_TIME} formatter.
-     *
-     * @param self a LocalDateTime
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String getDateTimeString(final LocalDateTime self) {
-        return self.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
-    }
-
-    /**
-     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE} formatter.
-     *
-     * @param self a LocalDateTime
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String getDateString(final LocalDateTime self) {
-        return self.format(DateTimeFormatter.ISO_LOCAL_DATE);
-    }
-
-    /**
-     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_TIME} formatter.
-     *
-     * @param self a LocalDateTime
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String getTimeString(final LocalDateTime self) {
-        return self.format(DateTimeFormatter.ISO_LOCAL_TIME);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalDateTime} with the time portion cleared.
-     *
-     * @param self a LocalDateTime
-     * @return a LocalDateTime
-     * @since 3.0
-     */
-    public static LocalDateTime clearTime(final LocalDateTime self) {
-        return self.truncatedTo(DAYS);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalDateTime} that is {@code seconds} seconds after this date/time.
-     *
-     * @param self    a LocalDateTime
-     * @param seconds the number of seconds to add
-     * @return a LocalDateTime
-     * @since 3.0
-     */
-    public static LocalDateTime plus(final LocalDateTime self, long seconds) {
-        return self.plusSeconds(seconds);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalDateTime} that is {@code seconds} seconds before this date/time.
-     *
-     * @param self    a LocalDateTime
-     * @param seconds the number of seconds to subtract
-     * @return a LocalDateTime
-     * @since 3.0
-     */
-    public static LocalDateTime minus(final LocalDateTime self, long seconds) {
-        return self.minusSeconds(seconds);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalDateTime} that is one second after this date/time.
-     *
-     * @param self a LocalDateTime
-     * @return a LocalDateTime
-     * @since 3.0
-     */
-    public static LocalDateTime next(final LocalDateTime self) {
-        return plus(self, 1);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalDateTime} that is one second before this date/time.
-     *
-     * @param self a LocalDateTime
-     * @return a LocalDateTime
-     * @since 3.0
-     */
-    public static LocalDateTime previous(final LocalDateTime self) {
-        return minus(self, 1);
-    }
-
-    /**
-     * Returns an {@link java.time.OffsetDateTime} of this date/time and the provided {@link java.time.ZoneOffset}.
-     *
-     * @param self   a LocalDateTime
-     * @param offset a ZoneOffset
-     * @return an OffsetDateTime
-     * @since 3.0
-     */
-    public static OffsetDateTime leftShift(final LocalDateTime self, ZoneOffset offset) {
-        return OffsetDateTime.of(self, offset);
-    }
-
-    /**
-     * Returns a {@link java.time.OffsetDateTime} of this date/time and the provided {@link java.time.ZoneId}.
-     *
-     * @param self a LocalDateTime
-     * @param zone a ZoneId
-     * @return a ZonedDateTime
-     * @since 3.0
-     */
-    public static ZonedDateTime leftShift(final LocalDateTime self, ZoneId zone) {
-        return ZonedDateTime.of(self, zone);
-    }
-
-    /**
-     * Returns a generally equivalent instance of {@link java.util.Date}.
-     * The time value of the returned date is truncated to milliseconds.
-     *
-     * @param self a LocalDateTime
-     * @return a java.util.Date
-     * @since 3.0
-     */
-    public static Date toDate(final LocalDateTime self) {
-        return toCalendar(self).getTime();
-    }
-
-    /**
-     * Returns a generally equivalent instance of {@link java.util.Calendar}.
-     * The time value of the returned calendar is truncated to milliseconds and the
-     * time zone is the current system default.
-     *
-     * @param self a LocalDateTime
-     * @return a java.util.Calendar
-     * @since 3.0
-     */
-    public static Calendar toCalendar(final LocalDateTime self) {
-        Calendar cal = Calendar.getInstance();
-        cal.set(Calendar.DATE, self.getDayOfMonth());
-        cal.set(Calendar.MONTH, self.getMonthValue() - 1);
-        cal.set(Calendar.YEAR, self.getYear());
-        cal.set(Calendar.HOUR_OF_DAY, self.getHour());
-        cal.set(Calendar.MINUTE, self.getMinute());
-        cal.set(Calendar.SECOND, self.getSecond());
-        cal.set(Calendar.MILLISECOND, millisFromNanos(self.getNano()));
-        return cal;
-    }
-
-    /* ******** java.time.LocalTime extension methods ******** */
-
-    /**
-     * Formats this time with the provided {@link java.time.format.DateTimeFormatter} pattern.
-     *
-     * @param self    a LocalDateTime
-     * @param pattern the formatting pattern
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String format(final LocalTime self, String pattern) {
-        return self.format(DateTimeFormatter.ofPattern(pattern));
-    }
-
-    /**
-     * Formats this time in the provided, localized {@link java.time.format.FormatStyle}.
-     *
-     * @param self      a LocalTime
-     * @param timeStyle the FormatStyle
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String format(final LocalTime self, FormatStyle timeStyle) {
-        return self.format(DateTimeFormatter.ofLocalizedTime(timeStyle));
-    }
-
-    /**
-     * Formats this time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_TIME} formatter.
-     *
-     * @param self a LocalTime
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String getTimeString(final LocalTime self) {
-        return self.format(DateTimeFormatter.ISO_LOCAL_TIME);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalTime} that is {@code seconds} seconds after this time.
-     *
-     * @param self    a LocalTime
-     * @param seconds the number of seconds to add
-     * @return a LocalTime
-     * @since 3.0
-     */
-    public static LocalTime plus(final LocalTime self, long seconds) {
-        return self.plusSeconds(seconds);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalTime} that is {@code seconds} seconds before this time.
-     *
-     * @param self    a LocalTime
-     * @param seconds the number of seconds to subtract
-     * @return a LocalTime
-     * @since 3.0
-     */
-    public static LocalTime minus(final LocalTime self, long seconds) {
-        return self.minusSeconds(seconds);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalTime} that is one second after this time.
-     *
-     * @param self a LocalTime
-     * @return a LocalTime
-     * @since 3.0
-     */
-    public static LocalTime next(final LocalTime self) {
-        return plus(self, 1);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalTime} that is one second before this time.
-     *
-     * @param self a LocalTime
-     * @return a LocalTime
-     * @since 3.0
-     */
-    public static LocalTime previous(final LocalTime self) {
-        return minus(self, 1);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalDateTime} of this time and the provided {@link java.time.LocalDate}.
-     *
-     * @param self a LocalTime
-     * @param date a LocalDate
-     * @return a LocalDateTime
-     * @since 3.0
-     */
-    public static LocalDateTime leftShift(final LocalTime self, LocalDate date) {
-        return LocalDateTime.of(date, self);
-    }
-
-    /**
-     * Returns an {@link java.time.OffsetTime} of this time and the provided {@link java.time.ZoneOffset}.
-     *
-     * @param self   a LocalTime
-     * @param offset a ZoneOffset
-     * @return an OffsetTime
-     * @since 3.0
-     */
-    public static OffsetTime leftShift(final LocalTime self, ZoneOffset offset) {
-        return OffsetTime.of(self, offset);
-    }
-
-    /**
-     * Returns a generally equivalent instance of {@link java.util.Date}. The day-month-year value of the
-     * returned date is today and the time is truncated to milliseconds.
-     *
-     * @param self a LocalTime
-     * @return a java.util.Date
-     * @since 3.0
-     */
-    public static Date toDate(final LocalTime self) {
-        return toCalendar(self).getTime();
-    }
-
-    /**
-     * Returns a generally equivalent instance of {@link java.util.Calendar}. The day-month-year value of the
-     * returned calendar is today, the time is truncated to milliseconds, and the time zone is the current
-     * system default.
-     *
-     * @param self a LocalTime
-     * @return a java.util.Calendar
-     * @since 3.0
-     */
-    public static Calendar toCalendar(final LocalTime self) {
-        Calendar cal = Calendar.getInstance();
-        cal.set(Calendar.HOUR_OF_DAY, self.getHour());
-        cal.set(Calendar.MINUTE, self.getMinute());
-        cal.set(Calendar.SECOND, self.getSecond());
-        cal.set(Calendar.MILLISECOND, millisFromNanos(self.getNano()));
-        return cal;
-    }
-
-    /* ******** java.time.MonthDay extension methods ******** */
-
-    /**
-     * Returns a {@link java.time.LocalDate} of this month/day and the provided year.
-     *
-     * @param self a MonthDay
-     * @param year a year
-     * @return a LocalDate
-     * @since 3.0
-     */
-    public static LocalDate leftShift(final MonthDay self, int year) {
-        return self.atYear(year);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalDate} of this month/day and the provided {@link java.time.Year}.
-     *
-     * @param self a MonthDay
-     * @param year a Year
-     * @return a LocalDate
-     * @since 3.0
-     */
-    public static LocalDate leftShift(final MonthDay self, Year year) {
-        return year.atMonthDay(self);
-    }
-
-    /* ******** java.time.OffsetDateTime extension methods ******** */
-
-    /**
-     * Formats this date/time with the provided {@link java.time.format.DateTimeFormatter} pattern.
-     *
-     * @param self    an OffsetDateTime
-     * @param pattern the formatting pattern
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String format(final OffsetDateTime self, String pattern) {
-        return self.format(DateTimeFormatter.ofPattern(pattern));
-    }
-
-    /**
-     * Formats this date/time in the provided, localized {@link java.time.format.FormatStyle}.
-     *
-     * @param self          an OffsetDateTime
-     * @param dateTimeStyle the FormatStyle
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String format(final OffsetDateTime self, FormatStyle dateTimeStyle) {
-        return self.format(DateTimeFormatter.ofLocalizedDateTime(dateTimeStyle));
-    }
-
-    /**
-     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_OFFSET_DATE_TIME} formatter.
-     *
-     * @param self an OffsetDateTime
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String getDateTimeString(final OffsetDateTime self) {
-        return self.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
-    }
-
-    /**
-     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_OFFSET_DATE} formatter.
-     *
-     * @param self an OffsetDateTime
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String getDateString(final OffsetDateTime self) {
-        return self.format(DateTimeFormatter.ISO_OFFSET_DATE);
-    }
-
-    /**
-     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_OFFSET_TIME} formatter.
-     *
-     * @param self an OffsetDateTime
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String getTimeString(final OffsetDateTime self) {
-        return self.format(DateTimeFormatter.ISO_OFFSET_TIME);
-    }
-
-    /**
-     * Returns an {@link java.time.OffsetDateTime} with the time portion cleared.
-     *
-     * @param self an OffsetDateTime
-     * @return an OffsetDateTime
-     * @since 3.0
-     */
-    public static OffsetDateTime clearTime(final OffsetDateTime self) {
-        return self.truncatedTo(DAYS);
-    }
-
-    /**
-     * Returns an {@link java.time.OffsetDateTime} that is {@code seconds} seconds after this date/time.
-     *
-     * @param self    an OffsetDateTime
-     * @param seconds the number of seconds to add
-     * @return an OffsetDateTime
-     * @since 3.0
-     */
-    public static OffsetDateTime plus(final OffsetDateTime self, long seconds) {
-        return self.plusSeconds(seconds);
-    }
-
-    /**
-     * Returns an {@link java.time.OffsetDateTime} that is {@code seconds} seconds before this date/time.
-     *
-     * @param self    an OffsetDateTime
-     * @param seconds the number of seconds to subtract
-     * @return an OffsetDateTime
-     * @since 3.0
-     */
-    public static OffsetDateTime minus(final OffsetDateTime self, long seconds) {
-        return self.minusSeconds(seconds);
-    }
-
-    /**
-     * Returns an {@link java.time.OffsetDateTime} one second after this date/time.
-     *
-     * @param self an OffsetDateTime
-     * @return an OffsetDateTime
-     * @since 3.0
-     */
-    public static OffsetDateTime next(final OffsetDateTime self) {
-        return plus(self, 1);
-    }
-
-    /**
-     * Returns an {@link java.time.OffsetDateTime} one second before this date/time.
-     *
-     * @param self an OffsetDateTime
-     * @return an OffsetDateTime
-     * @since 3.0
-     */
-    public static OffsetDateTime previous(final OffsetDateTime self) {
-        return minus(self, 1);
-    }
-
-    /**
-     * Returns a generally equivalent instance of {@link java.util.Date}.
-     * The time value of the returned date is truncated to milliseconds and will be
-     * adjusted to the current system default time zone.
-     *
-     * @param self an OffsetDateTime
-     * @return a java.util.Date
-     * @since 3.0
-     */
-    public static Date toDate(final OffsetDateTime self) {
-        return toCalendar(self).getTime();
-    }
-
-    /**
-     * Returns a generally equivalent instance of {@link java.util.Calendar}.
-     * The time value of the returned calendar is truncated to milliseconds and the time zone
-     * is based on the offset of this date/time.
-     *
-     * @param self an OffsetDateTime
-     * @return a java.util.Calendar
-     * @since 3.0
-     */
-    public static Calendar toCalendar(final OffsetDateTime self) {
-        return toCalendar(self.toZonedDateTime());
-    }
-
-    /* ******** java.time.OffsetTime extension methods ******** */
-
-    /**
-     * Formats this time with the provided {@link java.time.format.DateTimeFormatter} pattern.
-     *
-     * @param self    an OffsetTime
-     * @param pattern the formatting pattern
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String format(final OffsetTime self, String pattern) {
-        return self.format(DateTimeFormatter.ofPattern(pattern));
-    }
-
-    /**
-     * Formats this time in the provided, localized {@link java.time.format.FormatStyle}.
-     *
-     * @param self      an OffsetTime
-     * @param timeStyle the FormatStyle
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String format(final OffsetTime self, FormatStyle timeStyle) {
-        return self.format(DateTimeFormatter.ofLocalizedTime(timeStyle));
-    }
-
-    /**
-     * Formats this time with the {@link java.time.format.DateTimeFormatter#ISO_OFFSET_TIME} formatter.
-     *
-     * @param self an OffsetTime
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String getTimeString(final OffsetTime self) {
-        return self.format(DateTimeFormatter.ISO_OFFSET_TIME);
-    }
-
-    /**
-     * Returns an {@link java.time.OffsetTime} that is {@code seconds} seconds after this time.
-     *
-     * @param self    an OffsetTime
-     * @param seconds the number of seconds to add
-     * @return an OffsetTime
-     * @since 3.0
-     */
-    public static OffsetTime plus(final OffsetTime self, long seconds) {
-        return self.plusSeconds(seconds);
-    }
-
-    /**
-     * Returns an {@link java.time.OffsetTime} that is {@code seconds} seconds before this time.
-     *
-     * @param self    an OffsetTime
-     * @param seconds the number of seconds to subtract
-     * @return an OffsetTime
-     * @since 3.0
-     */
-    public static OffsetTime minus(final OffsetTime self, long seconds) {
-        return self.minusSeconds(seconds);
-    }
-
-    /**
-     * Returns an {@link java.time.OffsetTime} that is one second after this time.
-     *
-     * @param self an OffsetTime
-     * @return an OffsetTime
-     * @since 3.0
-     */
-    public static OffsetTime next(final OffsetTime self) {
-        return plus(self, 1);
-    }
-
-    /**
-     * Returns an {@link java.time.OffsetTime} that is one second before this time.
-     *
-     * @param self an OffsetTime
-     * @return an OffsetTime
-     * @since 3.0
-     */
-    public static OffsetTime previous(final OffsetTime self) {
-        return minus(self, 1);
-    }
-
-    /**
-     * Returns an {@link java.time.OffsetDateTime} of this time and the provided {@link java.time.LocalDate}.
-     *
-     * @param self an OffsetTime
-     * @param date a LocalDate
-     * @return an OffsetDateTime
-     * @since 3.0
-     */
-    public static OffsetDateTime leftShift(final OffsetTime self, LocalDate date) {
-        return OffsetDateTime.of(date, self.toLocalTime(), self.getOffset());
-    }
-
-    /**
-     * Returns a generally equivalent instance of {@link java.util.Date}.
-     * The time value of the returned date is truncated to milliseconds and will be
-     * adjusted to the current system default time zone.
-     *
-     * @param self an OffsetTime
-     * @return a java.util.Date
-     * @since 3.0
-     */
-    public static Date toDate(final OffsetTime self) {
-        return toCalendar(self).getTime();
-    }
-
-    /**
-     * Returns a generally equivalent instance of {@link java.util.Calendar}.
-     * The date value of the returned calendar is now, the time value is truncated to milliseconds,
-     * and the time zone is based on the offset of this time.
-     *
-     * @param self an OffsetTime
-     * @return a java.util.Calendar
-     * @since 3.0
-     */
-    public static Calendar toCalendar(final OffsetTime self) {
-        TimeZone timeZone = toTimeZone(self.getOffset());
-        Calendar cal = Calendar.getInstance(timeZone);
-        cal.set(Calendar.HOUR_OF_DAY, self.getHour());
-        cal.set(Calendar.MINUTE, self.getMinute());
-        cal.set(Calendar.SECOND, self.getSecond());
-        cal.set(Calendar.MILLISECOND, millisFromNanos(self.getNano()));
-        return cal;
-    }
-
-    /* ******** java.time.Period extension methods ******** */
-
-    /**
-     * Returns a {@link java.time.Period} that is {@code days} days longer than this period.
-     * No normalization is performed.
-     *
-     * @param self a Period
-     * @param days the number of days to increase this Period by
-     * @return a Period
-     * @since 3.0
-     */
-    public static Period plus(final Period self, long days) {
-        return self.plusDays(days);
-    }
-
-    /**
-     * Returns a {@link java.time.Period} that is {@code days} days shorter than this period.
-     * No normalization is performed.
-     *
-     * @param self a Period
-     * @param days the number of days to decrease this Period by
-     * @return a Period
-     * @since 3.0
-     */
-    public static Period minus(final Period self, long days) {
-        return self.minusDays(days);
-    }
-
-    /**
-     * Returns a {@link java.time.Period} that is one day longer than this period.
-     * No normalization is performed.
-     *
-     * @param self a Period
-     * @return a Period one day longer in length
-     * @since 3.0
-     */
-    public static Period next(final Period self) {
-        return plus(self, 1);
-    }
-
-    /**
-     * Returns a {@link java.time.Period} that is one day shorter than this period.
-     * No normalization is performed.
-     *
-     * @param self a Period
-     * @return a Period one day shorter in length
-     * @since 3.0
-     */
-    public static Period previous(final Period self) {
-        return minus(self, 1);
-    }
-
-    /**
-     * Supports the unary minus operator; equivalent to calling the {@link java.time.Period#negated()} method.
-     *
-     * @param self a Period
-     * @return a negated Period
-     * @since 3.0
-     */
-    public static Period negative(final Period self) {
-        return self.negated();
-    }
-
-    /**
-     * Supports the unary plus operator; returns a {@link java.time.Period} with all unit values positive.
-     * For example, a period of "2 years, -3 months, and -4 days" would result in a period of
-     * "2 years, 3 months, and 4 days." No normalization is performed.
-     *
-     * @param self a Period
-     * @return a positive Period
-     * @since 3.0
-     */
-    public static Period positive(final Period self) {
-        return !self.isNegative() ? self : self.withDays(Math.abs(self.getDays()))
-                .withMonths(Math.abs(self.getMonths()))
-                .withYears(Math.abs(self.getYears()));
-    }
-
-    /**
-     * Supports the multiply operator; equivalent to calling the {@link java.time.Period#multipliedBy(int)} method.
-     *
-     * @param self   a Period
-     * @param scalar a scalar to multiply each unit by
-     * @return a Period
-     * @since 3.0
-     */
-    public static Period multiply(final Period self, int scalar) {
-        return self.multipliedBy(scalar);
-    }
-
-    /**
-     * Returns true if this period is positive, excluding zero.
-     *
-     * @param self a ChronoPeriod
-     * @return true if positive
-     * @since 3.0
-     */
-    public static boolean isPositive(final ChronoPeriod self) {
-        return !self.isZero() && !self.isNegative();
-    }
-
-    /**
-     * Returns true if this period is zero or positive.
-     *
-     * @param self a ChronoPeriod
-     * @return true if nonnegative
-     * @since 3.0
-     */
-    public static boolean isNonnegative(final ChronoPeriod self) {
-        return self.isZero() || !self.isNegative();
-    }
-
-    /**
-     * Returns true if this period is zero or negative.
-     *
-     * @param self a ChronoPeriod
-     * @return true if nonpositive
-     * @since 3.0
-     */
-    public static boolean isNonpositive(final ChronoPeriod self) {
-        return self.isZero() || self.isNegative();
-    }
-
-    /* ******** java.time.Year extension methods ******** */
-
-    /**
-     * Returns a {@link java.time.Year} that is {@code years} years after this year.
-     *
-     * @param self  a Year
-     * @param years the number of years to add
-     * @return a Year
-     * @since 3.0
-     */
-    public static Year plus(final Year self, long years) {
-        return self.plusYears(years);
-    }
-
-    /**
-     * Returns a {@link java.time.Year} that is {@code years} years before this year.
-     *
-     * @param self  a Year
-     * @param years the number of years to subtract
-     * @return a Year
-     * @since 3.0
-     */
-    public static Year minus(final Year self, long years) {
-        return self.minusYears(years);
-    }
-
-    /**
-     * Returns a {@link java.time.Year} after this year.
-     *
-     * @param self  a Year
-     * @return the next Year
-     * @since 3.0
-     */
-    public static Year next(final Year self) {
-        return plus(self, 1);
-    }
-
-    /**
-     * Returns a {@link java.time.Year} before this year.
-     *
-     * @param self a Year
-     * @return the previous Year
-     * @since 3.0
-     */
-    public static Year previous(final Year self) {
-        return minus(self, 1);
-    }
-
-    /**
-     * Returns a {@link java.time.Period} between the first day of this year (inclusive) and the first day of the
-     * provided {@link java.time.Year} (exclusive).
-     *
-     * @param self a Year
-     * @param year another Year
-     * @return a Period between the Years
-     * @since 3.0
-     */
-    public static Period rightShift(final Year self, Year year) {
-        return Period.between(self.atDay(1), year.atDay(1));
-    }
-
-    /**
-     * Returns a {@link java.time.YearMonth} of this year and the provided {@link java.time.Month}.
-     *
-     * @param self  a Year
-     * @param month a Month
-     * @return a YearMonth
-     * @since 3.0
-     */
-    public static YearMonth leftShift(final Year self, Month month) {
-        return self.atMonth(month);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalDate} of this year on the given {@link java.time.MonthDay}.
-     *
-     * @param self     a Year
-     * @param monthDay a MonthDay
-     * @return a LocalDate
-     * @since 3.0
-     */
-    public static LocalDate leftShift(final Year self, MonthDay monthDay) {
-        return self.atMonthDay(monthDay);
-    }
-
-    /**
-     * Equivalent to calling the {@link java.time.Year#get(java.time.temporal.TemporalField)} method with a
-     * {@link java.time.temporal.ChronoField#ERA} argument.
-     *
-     * Returns the era of the year, which is currently either 0 (BC) or 1 (AD).
-     *
-     * @param self a Year
-     * @return an int representing the era
-     * @since 3.0
-     */
-    public static int getEra(final Year self) {
-        return self.get(ChronoField.ERA);
-    }
-
-    /**
-     * Equivalent to calling the {@link java.time.Year#get(java.time.temporal.TemporalField)} method with a
-     * {@link java.time.temporal.ChronoField#YEAR_OF_ERA} argument.
-     *
-     * Since Year=0 represents 1 BC, the yearOfEra value of Year=0 is 1, Year=-1 is 2, and so on.
-     *
-     * @param self a Year
-     * @return the year value of the era
-     * @since 3.0
-     */
-    public static int getYearOfEra(final Year self) {
-        return self.get(ChronoField.YEAR_OF_ERA);
-    }
-
-    /* ******** java.time.YearMonth extension methods ******** */
-
-    /**
-     * Returns a {@link java.time.YearMonth} that is {@code months} months after this year/month.
-     *
-     * @param self   a YearMonth
-     * @param months the number of months to add
-     * @return a Year
-     * @since 3.0
-     */
-    public static YearMonth plus(final YearMonth self, long months) {
-        return self.plusMonths(months);
-    }
-
-    /**
-     * Returns a {@link java.time.YearMonth} that is {@code months} months before this year/month.
-     *
-     * @param self   a YearMonth
-     * @param months the number of months to subtract
-     * @return a Year
-     * @since 3.0
-     */
-    public static YearMonth minus(final YearMonth self, long months) {
-        return self.minusMonths(months);
-    }
-
-    /**
-     * Returns a {@link java.time.YearMonth} that is the month after this year/month.
-     *
-     * @param self  a YearMonth
-     * @return the next YearMonth
-     * @since 3.0
-     */
-    public static YearMonth next(final YearMonth self) {
-        return plus(self, 1);
-    }
-
-    /**
-     * Returns a {@link java.time.YearMonth} that is the month before this year/month.
-     *
-     * @param self a YearMonth
-     * @return the previous YearMonth
-     * @since 3.0
-     */
-    public static YearMonth previous(final YearMonth self) {
-        return minus(self, 1);
-    }
-
-    /**
-     * Returns a {@link java.time.LocalDate} of this year/month and the given day of the month.
-     *
-     * @param self       a YearMonth
-     * @param dayOfMonth a day of the month
-     * @return a LocalDate
-     * @since 3.0
-     */
-    public static LocalDate leftShift(final YearMonth self, int dayOfMonth) {
-        return self.atDay(dayOfMonth);
-    }
-
-    /**
-     * Returns a {@link java.time.Period} of time between the first day of this year/month (inclusive) and the
-     * given {@link java.time.YearMonth} (exclusive).
-     *
-     * @param self  a YearMonth
-     * @param other another YearMonth
-     * @return a Period
-     * @since 3.0
-     */
-    public static Period rightShift(YearMonth self, YearMonth other) {
-        return Period.between(self.atDay(1), other.atDay(1));
-    }
-
-    /* ******** java.time.ZonedDateTime extension methods ******** */
-
-    /**
-     * Formats this date/time with the provided {@link java.time.format.DateTimeFormatter} pattern.
-     *
-     * @param self    a ZonedDateTime
-     * @param pattern the formatting pattern
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String format(final ZonedDateTime self, String pattern) {
-        return self.format(DateTimeFormatter.ofPattern(pattern));
-    }
-
-    /**
-     * Formats this date/time in the provided, localized {@link java.time.format.FormatStyle}.
-     *
-     * @param self          a ZonedDateTime
-     * @param dateTimeStyle the FormatStyle
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String format(final ZonedDateTime self, FormatStyle dateTimeStyle) {
-        return self.format(DateTimeFormatter.ofLocalizedDateTime(dateTimeStyle));
-    }
-
-    /**
-     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE_TIME} formatter
-     * and appends the zone's short name, e.g. {@code 2018-03-10T14:34:55.144EST}.
-     *
-     * @param self a ZonedDateTime
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String getDateTimeString(final ZonedDateTime self) {
-        return self.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + self.format(ZONE_SHORT_FORMATTER);
-    }
-
-    /**
-     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE} formatter
-     * and appends the zone's short name, e.g. {@code 2018-03-10EST}.
-     *
-     * @param self a ZonedDateTime
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String getDateString(final ZonedDateTime self) {
-        return self.format(DateTimeFormatter.ISO_LOCAL_DATE) + self.format(ZONE_SHORT_FORMATTER);
-    }
-
-    /**
-     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_TIME} formatter
-     * and appends the zone's short name, e.g. {@code 14:34:55.144EST}.
-     *
-     * @param self a ZonedDateTime
-     * @return a formatted String
-     * @see java.time.format.DateTimeFormatter
-     * @since 3.0
-     */
-    public static String getTimeString(final ZonedDateTime self) {
-        return self.format(DateTimeFormatter.ISO_LOCAL_TIME) + self.format(ZONE_SHORT_FORMATTER);
-    }
-
-    /**
-     * Returns an {@link java.time.ZonedDateTime} with the time portion cleared.
-     *
-     * @param self a ZonedDateTime
-     * @return a ZonedDateTime
-     * @since 3.0
-     */
-    public static ZonedDateTime clearTime(final ZonedDateTime self) {
-        return self.truncatedTo(DAYS);
-    }
-
-    /**
-     * Returns a {@link java.time.ZonedDateTime} that is {@code seconds} seconds after this date/time.
-     *
-     * @param self    an ZonedDateTime
-     * @param seconds the number of seconds to add
-     * @return a ZonedDateTime
-     * @since 3.0
-     */
-    public static ZonedDateTime plus(final ZonedDateTime self, long seconds) {
-        return self.plusSeconds(seconds);
-    }
-
-    /**
-     * Returns a {@link java.time.ZonedDateTime} that is {@code seconds} seconds before this date/time.
-     *
-     * @param self    a ZonedDateTime
-     * @param seconds the number of seconds to subtract
-     * @return a ZonedDateTime
-     * @since 3.0
-     */
-    public static ZonedDateTime minus(final ZonedDateTime self, long seconds) {
-        return self.minusSeconds(seconds);
-    }
-
-    /**
-     * Returns a {@link java.time.ZonedDateTime} that is one second after this date/time.
-     *
-     * @param self a ZonedDateTime
-     * @return a ZonedDateTime
-     * @since 3.0
-     */
-    public static ZonedDateTime next(final ZonedDateTime self) {
-        return plus(self, 1);
-    }
-
-    /**
-     * Returns a {@link java.time.ZonedDateTime} that is one second before this date/time.
-     *
-     * @param self a ZonedDateTime
-     * @return a ZonedDateTime
-     * @since 3.0
-     */
-    public static ZonedDateTime previous(final ZonedDateTime self) {
-        return minus(self, 1);
-    }
-
-    /**
-     * Returns a generally equivalent instance of {@link java.util.Date}.
-     * The time value of the returned date is truncated to milliseconds and will be
-     * adjusted to the current system default time zone.
-     *
-     * @param self a ZonedDateTime
-     * @return a java.util.Date
-     * @since 3.0
-     */
-    public static Date toDate(final ZonedDateTime self) {
-        return toCalendar(self).getTime();
-    }
-
-    /**
-     * Returns a generally equivalent instance of {@link java.util.Calendar}.
-     * The time value of the returned calendar is truncated to milliseconds and the time zone
-     * is determined by the zone of this date/time.
-     *
-     * @param self an ZonedDateTime
-     * @return a java.util.Calendar
-     * @since 3.0
-     */
-    public static Calendar toCalendar(final ZonedDateTime self) {
-        Calendar cal = Calendar.getInstance(toTimeZone(self.getZone()));
-        cal.set(Calendar.DATE, self.getDayOfMonth());
-        cal.set(Calendar.MONTH, self.getMonthValue() - 1);
-        cal.set(Calendar.YEAR, self.getYear());
-        cal.set(Calendar.HOUR_OF_DAY, self.getHour());
-        cal.set(Calendar.MINUTE, self.getMinute());
-        cal.set(Calendar.SECOND, self.getSecond());
-        cal.set(Calendar.MILLISECOND, millisFromNanos(self.getNano()));
-        return cal;
-    }
-
-    /* ******** java.time.ZoneId extension methods ******** */
-
-    /**
-     * Returns a {@link java.util.TimeZone} equivalent to this zone.
-     *
-     * @param self a ZoneId
-     * @return a TimeZone
-     * @since 3.0
-     */
-    public static TimeZone toTimeZone(final ZoneId self) {
-        return TimeZone.getTimeZone(self);
-    }
-
-    /**
-     * Returns the name of this zone formatted according to the {@link java.time.format.TextStyle#FULL} text style.
-     *
-     * @param self a ZoneId
-     * @return the full display name of the ZoneId
-     * @since 3.0
-     */
-    public static String getFullName(final ZoneId self) {
-        return getFullName(self, Locale.getDefault());
-    }
-
-    /**
-     * Returns the name of this zone formatted according to the {@link java.time.format.TextStyle#FULL} text style
-     * for the provided {@link java.util.Locale}.
-     *
-     * @param self   a ZoneId
-     * @param locale a Locale
-     * @return the full display name of the ZoneId
-     * @since 3.0
-     */
-    public static String getFullName(final ZoneId self, Locale locale) {
-        return self.getDisplayName(TextStyle.FULL, locale);
-    }
-
-    /**
-     * Returns the name of this zone formatted according to the {@link java.time.format.TextStyle#SHORT} text style.
-     *
-     * @param self   a ZoneId
-     * @return the short display name of the ZoneId
-     * @since 3.0
-     */
-    public static String getShortName(final ZoneId self) {
-        return getShortName(self, Locale.getDefault());
-    }
-
-    /**
-     * Returns the name of this zone formatted according to the {@link java.time.format.TextStyle#SHORT} text style
-     * for the provided {@link java.util.Locale}.
-     *
-     * @param self   a ZoneId
-     * @param locale a Locale
-     * @return the short display name of the ZoneId
-     * @since 3.0
-     */
-    public static String getShortName(final ZoneId self, Locale locale) {
-        return self.getDisplayName(TextStyle.SHORT, locale);
-    }
-
-    /**
-     * Returns a {@link java.time.ZoneOffset} for this zone as of now.
-     *
-     * @param self a ZoneId
-     * @return a ZoneOffset
-     * @since 3.0
-     */
-    public static ZoneOffset getOffset(final ZoneId self) {
-        return getOffset(self, Instant.now());
-    }
-
-    /**
-     * Returns a {@link java.time.ZoneOffset} for this zone as of the provided {@link java.time.Instant}.
-     *
-     * @param self    a ZoneId
-     * @param instant an Instant
-     * @return a ZoneOffset
-     * @since 3.0
-     */
-    public static ZoneOffset getOffset(final ZoneId self, Instant instant) {
-        return self.getRules().getOffset(instant);
-    }
-
-    /**
-     * Returns a {@link java.time.ZonedDateTime} of this zone and the given {@link java.time.LocalDateTime}.
-     *
-     * @param self a ZoneId
-     * @return a ZonedDateTime
-     * @since 3.0
-     */
-    public static ZonedDateTime leftShift(final ZoneId self, LocalDateTime dateTime) {
-        return ZonedDateTime.of(dateTime, self);
-    }
-
-    /* ******** java.time.ZoneOffset extension methods ******** */
-
-    /**
-     * Returns a generally  equivalent {@link java.util.TimeZone}. The offset will be truncated to minutes.
-     *
-     * @param self a ZoneOffset
-     * @return a TimeZone
-     * @since 3.0
-     */
-    public static TimeZone toTimeZone(final ZoneOffset self) {
-        if (ZoneOffset.UTC.equals(self)) {
-            return TimeZone.getTimeZone("GMT");
-        } else if (getSeconds(self) == 0) {
-            return TimeZone.getTimeZone("GMT" + self.getId());
-        } else {
-            // TimeZone is only hours and minutes--no seconds
-            ZoneOffset noSeconds = ZoneOffset.ofHoursMinutes(getHours(self), getMinutes(self));
-            return TimeZone.getTimeZone("GMT" + noSeconds.getId());
-        }
-    }
-
-    /**
-     * Returns the value of the provided field for the ZoneOffset as if the ZoneOffset's
-     * hours/minutes/seconds were reckoned as a LocalTime.
-     */
-    private static int offsetFieldValue(ZoneOffset offset, TemporalField field) {
-        int offsetSeconds = offset.getTotalSeconds();
-        int value = LocalTime.ofSecondOfDay(Math.abs(offsetSeconds)).get(field);
-        return offsetSeconds < 0 ? value * -1 : value;
-    }
-
-    /**
-     * Returns the hours component of this offset. If the offset's total seconds are negative, a negative
-     * value will be returned.
-     *
-     * @param self a ZoneOffset
-     * @return the hours component value
-     * @since 3.0
-     */
-    public static int getHours(final ZoneOffset self) {
-        return offsetFieldValue(self, ChronoField.HOUR_OF_DAY);
-    }
-
-    /**
-     * Returns the minutes component of this offset. If the offset's total seconds are negative, a negative
-     * value will be returned.
-     *
-     * @param self a ZoneOffset
-     * @return the minutes component value
-     * @since 3.0
-     */
-    public static int getMinutes(final ZoneOffset self) {
-        return offsetFieldValue(self, ChronoField.MINUTE_OF_HOUR);
-    }
-
-    /**
-     * Returns the seconds component of this offset. This is not the same as the total seconds. For example:
-     * <pre>
-     *     def offset = ZoneOffset.ofHoursMinutesSeconds(0, 1, 1)
-     *     assert offset.seconds == 1
-     *     assert offset.totalSeconds == 61
-     * </pre>
-     * <p>
-     * If the offset's total seconds are negative, a negative value will be returned.
-     *
-     * @param self a ZoneOffset
-     * @return the seconds component value
-     * @since 3.0
-     */
-    public static int getSeconds(final ZoneOffset self) {
-        return offsetFieldValue(self, ChronoField.SECOND_OF_MINUTE);
-    }
-
-    /**
-     * Supports the getAt operator; equivalent to calling the
-     * {@link java.time.ZoneOffset#getLong(java.time.temporal.TemporalField)} method.
-     *
-     * @param self  a ZoneOffset
-     * @param field a TemporalField
-     * @return the ZoneOffset's field value
-     * @since 3.0
-     */
-    public static long getAt(final ZoneOffset self, TemporalField field) {
-        return self.getLong(field);
-    }
-
-    /**
-     * Returns an {@link java.time.OffsetDateTime} of this offset and the provided {@link java.time.LocalDateTime}.
-     *
-     * @param self     a ZoneOffset
-     * @param dateTime a LocalDateTime
-     * @return an OffsetDateTime
-     * @since 3.0
-     */
-    public static OffsetDateTime leftShift(final ZoneOffset self, LocalDateTime dateTime) {
-        return OffsetDateTime.of(dateTime, self);
-    }
-
-    /**
-     * Returns an {@link java.time.OffsetDateTime} of this offset and the provided {@link java.time.LocalTime}.
-     *
-     * @param self a ZoneOffset
-     * @param time a LocalTime
-     * @return an OffsetTime
-     * @since 3.0
-     */
-    public static OffsetTime leftShift(final ZoneOffset self, LocalTime time) {
-        return OffsetTime.of(time, self);
-    }
-
-    /* ******** java.time.DayOfWeek extension methods ******** */
-
-    /**
-     * Returns the {@link java.time.DayOfWeek} that is {@code days} many days after this day of the week.
-     *
-     * @param self a DayOfWeek
-     * @param days the number of days to move forward
-     * @return the DayOfWeek
-     * @since 3.0
-     */
-    public static DayOfWeek plus(final DayOfWeek self, int days) {
-        int daysPerWeek = DayOfWeek.values().length;
-        int val = ((self.getValue() + days - 1) % daysPerWeek) + 1;
-        return DayOfWeek.of(val > 0 ? val : daysPerWeek + val);
-    }
-
-    /**
-     * Returns the {@link java.time.DayOfWeek} that is {@code days} many days before this day of the week.
-     *
-     * @param self a DayOfWeek
-     * @param days the number of days to move back
-     * @return the DayOfWeek
-     * @since 3.0
-     */
-    public static DayOfWeek minus(final DayOfWeek self, int days) {
-        return plus(self, days * -1);
-    }
-
-    /**
-     * Returns {@code true} if this day of the week is a weekend day (Saturday or Sunday).
-     *
-     * @param self a DayOfWeek
-     * @return true if this DayOfWeek is Saturday or Sunday
-     * @since 3.0
-     */
-    public static boolean isWeekend(final DayOfWeek self) {
-        return self == DayOfWeek.SATURDAY || self == DayOfWeek.SUNDAY;
-    }
-
-    /**
-     * Returns {@code true} if the DayOfWeek is a weekday.
-     *
-     * @return true if this DayOfWeek is Monday through Friday
-     * @since 3.0
-     */
-    public static boolean isWeekday(final DayOfWeek self) {
-        return !isWeekend(self);
-    }
-
-    /* ******** java.time.Month extension methods ******** */
-
-    /**
-     * Returns the {@link java.time.Month} that is {@code months} months after this month.
-     *
-     * @param self   a Month
-     * @param months the number of months move forward
-     * @return the Month
-     * @since 3.0
-     */
-    public static Month plus(final Month self, int months) {
-        int monthsPerYear = Month.values().length;
-        int val = ((self.getValue() + months - 1) % monthsPerYear) + 1;
-        return Month.of(val > 0 ? val : monthsPerYear + val);
-    }
-
-    /**
-     * Returns the {@link java.time.Month} that is {@code months} months before this month.
-     *
-     * @param self   a Month
-     * @param months the number of months to move back
-     * @return the Month
-     * @since 3.0
-     */
-    public static Month minus(final Month self, int months) {
-        return plus(self, months * -1);
-    }
-
-    /**
-     * Creates a {@link java.time.MonthDay} at the provided day of the month.
-     *
-     * @param self       a Month
-     * @param dayOfMonth a day of the month
-     * @return a MonthDay
-     * @since 3.0
-     */
-    public static MonthDay leftShift(final Month self, int dayOfMonth) {
-        return MonthDay.of(self, dayOfMonth);
-    }
-
-    /**
-     * Creates a {@link java.time.YearMonth} at the provided {@link java.time.Year}.
-     *
-     * @param self a Month
-     * @param year a Year
-     * @return a YearMonth
-     * @since 3.0
-     */
-    public static YearMonth leftShift(final Month self, Year year) {
-        return YearMonth.of(year.getValue(), self);
-    }
-}

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index 1a356a5..fedaa29 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -220,15 +220,16 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
 
     public static final Class[] DGM_LIKE_CLASSES = new Class[]{
             DefaultGroovyMethods.class,
-            DateGroovyMethods.class,
-            DateTimeGroovyMethods.class,
             EncodingGroovyMethods.class,
             IOGroovyMethods.class,
             ProcessGroovyMethods.class,
             ResourceGroovyMethods.class,
             SocketGroovyMethods.class,
             StringGroovyMethods.class//,
-            // TODO provide alternative way for these to be registered
+            // Below are registered as module extension classes
+//            DateUtilExtensions.class,
+//            DateTimeStaticExtensions.class,
+//            DateTimeExtensions.class,
 //            SqlGroovyMethods.class,
 //            SwingGroovyMethods.class,
 //            XmlGroovyMethods.class,

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
index ac69c59..421649f 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
@@ -304,208 +304,4 @@ public class DefaultGroovyStaticMethods {
     return System.currentTimeMillis() / 1000;
   }
 
-    /**
-     * Parse text into a {@link java.time.LocalDate} using the provided pattern.
-     *
-     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
-     * @param text    String to be parsed to create the date instance
-     * @param pattern pattern used to parse the text
-     * @return a LocalDate representing the parsed text
-     * @throws java.lang.IllegalArgumentException if the pattern is invalid
-     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
-     * @see java.time.format.DateTimeFormatter
-     * @see java.time.LocalDate#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
-     * @since 3.0
-     */
-    public static LocalDate parse(final LocalDate type, CharSequence text, String pattern) {
-        return LocalDate.parse(text, DateTimeFormatter.ofPattern(pattern));
-    }
-
-    /**
-     * Parse text into a {@link java.time.LocalDateTime} using the provided pattern.
-     *
-     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
-     * @param text    String to be parsed to create the date instance
-     * @param pattern pattern used to parse the text
-     * @return a LocalDateTime representing the parsed text
-     * @throws java.lang.IllegalArgumentException if the pattern is invalid
-     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
-     * @see java.time.format.DateTimeFormatter
-     * @see java.time.LocalDateTime#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
-     * @since 3.0
-     */
-    public static LocalDateTime parse(final LocalDateTime type, CharSequence text, String pattern) {
-        return LocalDateTime.parse(text, DateTimeFormatter.ofPattern(pattern));
-    }
-
-    /**
-     * Parse text into a {@link java.time.LocalTime} using the provided pattern.
-     *
-     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
-     * @param text    String to be parsed to create the date instance
-     * @param pattern pattern used to parse the text
-     * @return a LocalTime representing the parsed text
-     * @throws java.lang.IllegalArgumentException if the pattern is invalid
-     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
-     * @see java.time.format.DateTimeFormatter
-     * @see java.time.LocalTime#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
-     * @since 3.0
-     */
-    public static LocalTime parse(final LocalTime type, CharSequence text, String pattern) {
-        return LocalTime.parse(text, DateTimeFormatter.ofPattern(pattern));
-    }
-
-    /**
-     * Parse text into a {@link java.time.MonthDay} using the provided pattern.
-     *
-     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
-     * @param text    String to be parsed to create the date instance
-     * @param pattern pattern used to parse the text
-     * @return a MonthDay representing the parsed text
-     * @throws java.lang.IllegalArgumentException if the pattern is invalid
-     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
-     * @see java.time.format.DateTimeFormatter
-     * @see java.time.MonthDay#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
-     * @since 3.0
-     */
-    public static MonthDay parse(final MonthDay type, CharSequence text, String pattern) {
-        return MonthDay.parse(text, DateTimeFormatter.ofPattern(pattern));
-    }
-
-    /**
-     * Parse text into an {@link java.time.OffsetDateTime} using the provided pattern.
-     *
-     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
-     * @param text    String to be parsed to create the date instance
-     * @param pattern pattern used to parse the text
-     * @return an OffsetDateTime representing the parsed text
-     * @throws java.lang.IllegalArgumentException if the pattern is invalid
-     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
-     * @see java.time.format.DateTimeFormatter
-     * @see java.time.OffsetDateTime#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
-     * @since 3.0
-     */
-    public static OffsetDateTime parse(final OffsetDateTime type, CharSequence text, String pattern) {
-        return OffsetDateTime.parse(text, DateTimeFormatter.ofPattern(pattern));
-    }
-
-    /**
-     * Parse text into an {@link java.time.OffsetTime} using the provided pattern.
-     *
-     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
-     * @param text    String to be parsed to create the date instance
-     * @param pattern pattern used to parse the text
-     * @return an OffsetTime representing the parsed text
-     * @throws java.lang.IllegalArgumentException if the pattern is invalid
-     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
-     * @see java.time.format.DateTimeFormatter
-     * @see java.time.OffsetTime#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
-     * @since 3.0
-     */
-    public static OffsetTime parse(final OffsetTime type, CharSequence text, String pattern) {
-        return OffsetTime.parse(text, DateTimeFormatter.ofPattern(pattern));
-    }
-
-    /**
-     * Parse text into a {@link java.time.Year} using the provided pattern.
-     *
-     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
-     * @param text    String to be parsed to create the date instance
-     * @param pattern pattern used to parse the text
-     * @return a Year representing the parsed text
-     * @throws java.lang.IllegalArgumentException if the pattern is invalid
-     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
-     * @see java.time.format.DateTimeFormatter
-     * @see java.time.Year#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
-     * @since 3.0
-     */
-    public static Year parse(final Year type, CharSequence text, String pattern) {
-        return Year.parse(text, DateTimeFormatter.ofPattern(pattern));
-    }
-
-    /**
-     * Parse text into a {@link java.time.YearMonth} using the provided pattern.
-     *
-     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
-     * @param text    String to be parsed to create the date instance
-     * @param pattern pattern used to parse the text
-     * @return a YearMonth representing the parsed text
-     * @throws java.lang.IllegalArgumentException if the pattern is invalid
-     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
-     * @see java.time.format.DateTimeFormatter
-     * @see java.time.YearMonth#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
-     * @since 3.0
-     */
-    public static YearMonth parse(final YearMonth type, CharSequence text, String pattern) {
-        return YearMonth.parse(text, DateTimeFormatter.ofPattern(pattern));
-    }
-
-    /**
-     * Parse text into a {@link java.time.ZonedDateTime} using the provided pattern.
-     *
-     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
-     * @param text    String to be parsed to create the date instance
-     * @param pattern pattern used to parse the text
-     * @return a ZonedDateTime representing the parsed text
-     * @throws java.lang.IllegalArgumentException if the pattern is invalid
-     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
-     * @see java.time.format.DateTimeFormatter
-     * @see java.time.ZonedDateTime#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
-     * @since 3.0
-     */
-    public static ZonedDateTime parse(final ZonedDateTime type, CharSequence text, String pattern) {
-        return ZonedDateTime.parse(text, DateTimeFormatter.ofPattern(pattern));
-    }
-
-    /**
-     * Returns the {@link java.time.ZoneOffset} currently associated with the system default {@link java.time.ZoneId}.
-     *
-     * @param type placeholder variable used by Groovy categories; ignored for default static methods
-     * @return a ZoneOffset
-     * @see java.time.ZoneId#systemDefault()
-     * @since 3.0
-     */
-    public static ZoneOffset systemDefault(final ZoneOffset type) {
-        return DateTimeGroovyMethods.getOffset(ZoneId.systemDefault());
-    }
-
-    /**
-     * Obtains a Period consisting of the number of years between two {@link java.time.Year} instances.
-     * The months and days of the Period will be zero.
-     * The result of this method can be a negative period if the end is before the start.
-     *
-     * @param type           placeholder variable used by Groovy categories; ignored for default static methods
-     * @param startInclusive the start {@link java.time.Year}, inclusive, not null
-     * @param endExclusive   the end {@link java.time.Year}, exclusive, not null
-     * @return a Period between the years
-     * @see java.time.Period#between(LocalDate, LocalDate)
-     */
-    public static Period between(final Period type, Year startInclusive, Year endExclusive) {
-        MonthDay now = MonthDay.of(Month.JANUARY, 1);
-        return Period.between(
-                DateTimeGroovyMethods.leftShift(startInclusive, now),
-                DateTimeGroovyMethods.leftShift(endExclusive, now))
-                .withDays(0)
-                .withMonths(0);
-    }
-
-    /**
-     * Obtains a Period consisting of the number of years and months between two {@link java.time.YearMonth} instances.
-     * The days of the Period will be zero.
-     * The result of this method can be a negative period if the end is before the start.
-     *
-     * @param type           placeholder variable used by Groovy categories; ignored for default static methods
-     * @param startInclusive the start {@link java.time.YearMonth}, inclusive, not null
-     * @param endExclusive   the end {@link java.time.YearMonth}, exclusive, not null
-     * @return a Period between the year/months
-     * @see java.time.Period#between(LocalDate, LocalDate)
-     */
-    public static Period between(final Period type, YearMonth startInclusive, YearMonth endExclusive) {
-        int dayOfMonth = 1;
-        return Period.between(
-                DateTimeGroovyMethods.leftShift(startInclusive, dayOfMonth),
-                DateTimeGroovyMethods.leftShift(endExclusive, dayOfMonth))
-                .withDays(0);
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/src/spec/doc/core-gdk.adoc
----------------------------------------------------------------------
diff --git a/src/spec/doc/core-gdk.adoc b/src/spec/doc/core-gdk.adoc
index 2d239fe..7a79e0b 100644
--- a/src/spec/doc/core-gdk.adoc
+++ b/src/spec/doc/core-gdk.adoc
@@ -25,7 +25,9 @@ include::{projectdir}/src/spec/doc/working-with-io.adoc[leveloffset=+1]
 
 include::{projectdir}/src/spec/doc/working-with-collections.adoc[leveloffset=+1]
 
-include::{projectdir}/src/spec/doc/working-with-datetime-types.adoc[leveloffset=+1]
+include::{projectdir}/subprojects/groovy-dateutil/{specfolder}/working-with-dateutil-types.adoc[leveloffset=+1]
+
+include::{projectdir}/subprojects/groovy-datetime/{specfolder}/working-with-datetime-types.adoc[leveloffset=+1]
 
 == Handy utilities
 


[06/14] groovy git commit: fix cut-n-paste typo in javadoc

Posted by pa...@apache.org.
fix cut-n-paste typo in javadoc


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/8d261b9b
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/8d261b9b
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/8d261b9b

Branch: refs/heads/GROOVY_2_6_X
Commit: 8d261b9bbb0ac95a67d2a916ab7d561815dc6207
Parents: 963c90b
Author: paulk <pa...@asert.com.au>
Authored: Tue Mar 20 11:33:38 2018 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Thu Mar 22 00:41:47 2018 +1000

----------------------------------------------------------------------
 .../groovy-test/src/main/java/groovy/test/GroovyAssert.java      | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/8d261b9b/subprojects/groovy-test/src/main/java/groovy/test/GroovyAssert.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-test/src/main/java/groovy/test/GroovyAssert.java b/subprojects/groovy-test/src/main/java/groovy/test/GroovyAssert.java
index 3c57a19..3b51b3c 100644
--- a/subprojects/groovy-test/src/main/java/groovy/test/GroovyAssert.java
+++ b/subprojects/groovy-test/src/main/java/groovy/test/GroovyAssert.java
@@ -292,7 +292,7 @@ public class GroovyAssert extends org.junit.Assert {
      *
      * <pre>
      * public void testXXX() {
-     *   if (GroovyTestCase.notYetImplemented(this)) return;
+     *   if (GroovyAssert.notYetImplemented(this)) return;
      *   ... the real (now failing) unit test
      * }
      * </pre>
@@ -302,7 +302,7 @@ public class GroovyAssert extends org.junit.Assert {
      * <pre>
      * &#64;Test
      * public void XXX() {
-     *   if (GroovyTestCase.notYetImplemented(this)) return;
+     *   if (GroovyAssert.notYetImplemented(this)) return;
      *   ... the real (now failing) unit test
      * }
      * </pre>


[03/14] groovy git commit: Fix upto/downto with custom TemporalUnit arg edge cases. Add Period and Duration methods.

Posted by pa...@apache.org.
Fix upto/downto with custom TemporalUnit arg edge cases. Add Period and Duration methods.


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/92378be9
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/92378be9
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/92378be9

Branch: refs/heads/GROOVY_2_6_X
Commit: 92378be9fce96d6cd125966ad926854b82d0bcee
Parents: 99db9bf
Author: Joe Wolf <jo...@gmail.com>
Authored: Sun Mar 4 16:55:56 2018 -0500
Committer: paulk <pa...@asert.com.au>
Committed: Thu Mar 22 00:41:46 2018 +1000

----------------------------------------------------------------------
 .../groovy/runtime/DateTimeGroovyMethods.java   | 136 ++++++++++--
 .../runtime/DefaultGroovyStaticMethods.java     |  57 +++++-
 src/test/groovy/DateTimeTest.groovy             | 205 ++++++++++++++-----
 3 files changed, 327 insertions(+), 71 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/92378be9/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
index 947e368..97d7df7 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
@@ -23,12 +23,17 @@ import groovy.lang.GroovyRuntimeException;
 
 import java.time.*;
 import java.time.chrono.ChronoLocalDate;
+import java.time.chrono.ChronoPeriod;
 import java.time.format.DateTimeFormatter;
 import java.time.format.FormatStyle;
 import java.time.format.TextStyle;
 import java.time.temporal.*;
 import java.util.*;
 
+import static java.time.temporal.ChronoUnit.DAYS;
+import static java.time.temporal.ChronoUnit.MONTHS;
+import static java.time.temporal.ChronoUnit.YEARS;
+
 /**
  * This class defines new Groovy methods which appear on normal JDK
  * Date/Time API (java.time) classes inside the Groovy environment.
@@ -47,9 +52,9 @@ public class DateTimeGroovyMethods {
      */
     private static Map<Class<? extends Temporal>, TemporalUnit> DEFAULT_UNITS = new HashMap<>();
     static {
-        DEFAULT_UNITS.put(ChronoLocalDate.class, ChronoUnit.DAYS);
-        DEFAULT_UNITS.put(YearMonth.class, ChronoUnit.MONTHS);
-        DEFAULT_UNITS.put(Year.class, ChronoUnit.YEARS);
+        DEFAULT_UNITS.put(ChronoLocalDate.class, DAYS);
+        DEFAULT_UNITS.put(YearMonth.class, MONTHS);
+        DEFAULT_UNITS.put(Year.class, YEARS);
     }
 
     /**
@@ -69,14 +74,15 @@ public class DateTimeGroovyMethods {
      * Truncates a nanosecond value to milliseconds. No rounding.
      */
     private static int millisFromNanos(int nanos) {
-       return nanos / 1_000_000;
+        return nanos / 1_000_000;
     }
 
     /* ******** java.time.temporal.Temporal extension methods ******** */
 
     /**
-     * Iterates from the this to {@code to}, inclusive, incrementing by one unit each iteration, calling the
-     * closure once per iteration. The closure may accept a single {@link java.time.temporal.Temporal} argument.
+     * Iterates from this to the {@code to} {@link java.time.temporal.Temporal}, inclusive, incrementing by one
+     * unit each iteration, calling the closure once per iteration. The closure may accept a single
+     * {@link java.time.temporal.Temporal} argument.
      * <p>
      * The particular unit incremented by depends on the specific sub-type of {@link java.time.temporal.Temporal}.
      * Most sub-types use a unit of {@link java.time.temporal.ChronoUnit#SECONDS} except for
@@ -97,8 +103,8 @@ public class DateTimeGroovyMethods {
     }
 
     /**
-     * Iterates from this to {@code to}, inclusive, incrementing by one {@code unit} each iteration,
-     * calling the closure once per iteration. The closure may accept a single
+     * Iterates from this to the {@code to} {@link java.time.temporal.Temporal}, inclusive, incrementing by one
+     * {@code unit} each iteration, calling the closure once per iteration. The closure may accept a single
      * {@link java.time.temporal.Temporal} argument.
      *
      * If the unit is too large to iterate to the second Temporal exactly, such as iterating from two LocalDateTimes
@@ -113,8 +119,8 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static void upto(Temporal from, Temporal to, TemporalUnit unit, Closure closure) {
-        if (from.until(to, unit) >= 0) {
-            for (Temporal i = from; i.until(to, unit) >= 0; i = i.plus(1, unit)) {
+        if (isUptoEligible(from, to)) {
+            for (Temporal i = from; isUptoEligible(i, to); i = i.plus(1, unit)) {
                 closure.call(i);
             }
         } else {
@@ -124,10 +130,26 @@ public class DateTimeGroovyMethods {
     }
 
     /**
+     * Returns true if the {@code from} can be iterated up to {@code to}.
+     */
+    private static boolean isUptoEligible(Temporal from, Temporal to) {
+        switch ((ChronoUnit) defaultUnitFor(from)) {
+            case YEARS:
+                return isNonnegative(DefaultGroovyStaticMethods.between(null, (Year) from, (Year) to));
+            case MONTHS:
+                return isNonnegative(DefaultGroovyStaticMethods.between(null, (YearMonth) from, (YearMonth) to));
+            case DAYS:
+                return isNonnegative(ChronoPeriod.between((ChronoLocalDate) from, (ChronoLocalDate) to));
+            default:
+                return isNonnegative(Duration.between(from, to));
+        }
+    }
+
+    /**
      * Iterates from this to the {@code to} {@link java.time.temporal.Temporal}, inclusive, decrementing by one
      * unit each iteration, calling the closure once per iteration. The closure may accept a single
      * {@link java.time.temporal.Temporal} argument.
-     *
+     * <p>
      * The particular unit decremented by depends on the specific sub-type of {@link java.time.temporal.Temporal}.
      * Most sub-types use a unit of {@link java.time.temporal.ChronoUnit#SECONDS} except for
      * <ul>
@@ -163,8 +185,8 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static void downto(Temporal from, Temporal to, TemporalUnit unit, Closure closure) {
-        if (from.until(to, unit) <= 0) {
-            for (Temporal i = from; i.until(to, unit) <= 0; i = i = i.minus(1, unit)) {
+        if (isDowntoEligible(from, to)) {
+            for (Temporal i = from; isDowntoEligible(i, to); i = i.minus(1, unit)) {
                 closure.call(i);
             }
         } else {
@@ -174,6 +196,22 @@ public class DateTimeGroovyMethods {
     }
 
     /**
+     * Returns true if the {@code from} can be iterated down to {@code to}.
+     */
+    private static boolean isDowntoEligible(Temporal from, Temporal to) {
+        switch ((ChronoUnit) defaultUnitFor(from)) {
+            case YEARS:
+                return isNonpositive(DefaultGroovyStaticMethods.between(null, (Year) from, (Year) to));
+            case MONTHS:
+                return isNonpositive(DefaultGroovyStaticMethods.between(null, (YearMonth) from, (YearMonth) to));
+            case DAYS:
+                return isNonpositive(ChronoPeriod.between((ChronoLocalDate) from, (ChronoLocalDate) to));
+            default:
+                return isNonpositive(Duration.between(from, to));
+        }
+    }
+
+    /**
      * Returns a {@link java.time.Duration} of time between this (inclusive) and {@code other} (exclusive).
      *
      * @param self  a Temporal
@@ -315,6 +353,39 @@ public class DateTimeGroovyMethods {
         return self.dividedBy(scalar);
     }
 
+    /**
+     * Returns true if this duration is positive, excluding zero.
+     *
+     * @param self a Duration
+     * @return true if positive
+     * @since 3.0
+     */
+    public static boolean isPositive(final Duration self) {
+        return !self.isZero() && !self.isNegative();
+    }
+
+    /**
+     * Returns true if this duration is zero or positive.
+     *
+     * @param self a Duration
+     * @return true if nonnegative
+     * @since 3.0
+     */
+    public static boolean isNonnegative(final Duration self) {
+        return self.isZero() || !self.isNegative();
+    }
+
+    /**
+     * Returns true if this duration is zero or negative.
+     *
+     * @param self a Duration
+     * @return true if nonpositive
+     * @since 3.0
+     */
+    public static boolean isNonpositive(final Duration self) {
+        return self.isZero() || self.isNegative();
+    }
+
     /* ******** java.time.Instant extension methods ******** */
 
     /**
@@ -613,7 +684,7 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static LocalDateTime clearTime(final LocalDateTime self) {
-        return self.truncatedTo(ChronoUnit.DAYS);
+        return self.truncatedTo(DAYS);
     }
 
     /**
@@ -959,7 +1030,7 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static OffsetDateTime clearTime(final OffsetDateTime self) {
-        return self.truncatedTo(ChronoUnit.DAYS);
+        return self.truncatedTo(DAYS);
     }
 
     /**
@@ -1254,6 +1325,39 @@ public class DateTimeGroovyMethods {
         return self.multipliedBy(scalar);
     }
 
+    /**
+     * Returns true if this period is positive, excluding zero.
+     *
+     * @param self a ChronoPeriod
+     * @return true if positive
+     * @since 3.0
+     */
+    public static boolean isPositive(final ChronoPeriod self) {
+        return !self.isZero() && !self.isNegative();
+    }
+
+    /**
+     * Returns true if this period is zero or positive.
+     *
+     * @param self a ChronoPeriod
+     * @return true if nonnegative
+     * @since 3.0
+     */
+    public static boolean isNonnegative(final ChronoPeriod self) {
+        return self.isZero() || !self.isNegative();
+    }
+
+    /**
+     * Returns true if this period is zero or negative.
+     *
+     * @param self a ChronoPeriod
+     * @return true if nonpositive
+     * @since 3.0
+     */
+    public static boolean isNonpositive(final ChronoPeriod self) {
+        return self.isZero() || self.isNegative();
+    }
+
     /* ******** java.time.Year extension methods ******** */
 
     /**
@@ -1514,7 +1618,7 @@ public class DateTimeGroovyMethods {
      * @since 3.0
      */
     public static ZonedDateTime clearTime(final ZonedDateTime self) {
-        return self.truncatedTo(ChronoUnit.DAYS);
+        return self.truncatedTo(DAYS);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/groovy/blob/92378be9/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
index 55d3d07..0ad2f33 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
@@ -299,15 +299,15 @@ public class DefaultGroovyStaticMethods {
         return tempFile;
     }
 
-  /**
-   * Get the current time in seconds
-   *
-   * @param self   placeholder variable used by Groovy categories; ignored for default static methods
-   * @return  the difference, measured in seconds, between
-   *          the current time and midnight, January 1, 1970 UTC.
-   * @see     System#currentTimeMillis()
-   */
-  public static long currentTimeSeconds(System self){
+    /**
+     * Get the current time in seconds
+     *
+     * @param self   placeholder variable used by Groovy categories; ignored for default static methods
+     * @return  the difference, measured in seconds, between
+     *          the current time and midnight, January 1, 1970 UTC.
+     * @see     System#currentTimeMillis()
+     */
+    public static long currentTimeSeconds(System self){
     return System.currentTimeMillis() / 1000;
   }
 
@@ -476,4 +476,43 @@ public class DefaultGroovyStaticMethods {
         return DateTimeGroovyMethods.getOffset(ZoneId.systemDefault());
     }
 
+    /**
+     * Obtains a Period consisting of the number of years between two {@link java.time.Year} instances.
+     * The months and days of the Period will be zero.
+     * The result of this method can be a negative period if the end is before the start.
+     *
+     * @param type           placeholder variable used by Groovy categories; ignored for default static methods
+     * @param startInclusive the start {@link java.time.Year}, inclusive, not null
+     * @param endExclusive   the end {@link java.time.Year}, exclusive, not null
+     * @return a Period between the years
+     * @see java.time.Period#between(LocalDate, LocalDate)
+     */
+    public static Period between(final Period type, Year startInclusive, Year endExclusive) {
+        MonthDay now = MonthDay.of(Month.JANUARY, 1);
+        return Period.between(
+                DateTimeGroovyMethods.leftShift(startInclusive, now),
+                DateTimeGroovyMethods.leftShift(endExclusive, now))
+                .withDays(0)
+                .withMonths(0);
+    }
+
+    /**
+     * Obtains a Period consisting of the number of years and months between two {@link java.time.YearMonth} instances.
+     * The days of the Period will be zero.
+     * The result of this method can be a negative period if the end is before the start.
+     *
+     * @param type           placeholder variable used by Groovy categories; ignored for default static methods
+     * @param startInclusive the start {@link java.time.YearMonth}, inclusive, not null
+     * @param endExclusive   the end {@link java.time.YearMonth}, exclusive, not null
+     * @return a Period between the year/months
+     * @see java.time.Period#between(LocalDate, LocalDate)
+     */
+    public static Period between(final Period type, YearMonth startInclusive, YearMonth endExclusive) {
+        int dayOfMonth = 1;
+        return Period.between(
+                DateTimeGroovyMethods.leftShift(startInclusive, dayOfMonth),
+                DateTimeGroovyMethods.leftShift(endExclusive, dayOfMonth))
+                .withDays(0);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/92378be9/src/test/groovy/DateTimeTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/groovy/DateTimeTest.groovy b/src/test/groovy/DateTimeTest.groovy
index 088f460..46eae17 100644
--- a/src/test/groovy/DateTimeTest.groovy
+++ b/src/test/groovy/DateTimeTest.groovy
@@ -170,6 +170,22 @@ class DateTimeTest extends GroovyTestCase {
         assert (duration * 2).seconds == 120
     }
 
+    void testDurationIsPositiveIsNonnegativeIsNonpositive() {
+        def pos = Duration.ofSeconds(10)
+        assert pos.isPositive() == true
+        assert pos.isNonpositive() == false
+        assert pos.isNonnegative() == true
+
+        def neg = Duration.ofSeconds(-10)
+        assert neg.isPositive() == false
+        assert neg.isNonpositive() == true
+        assert neg.isNonnegative() == false
+
+        assert Duration.ZERO.isPositive() == false
+        assert Duration.ZERO.isNonpositive() == true
+        assert Duration.ZERO.isNonnegative() == true
+    }
+
     void testPeriodPositiveNegative() {
         def positivePeriod = Period.of(1,2,3)
         Period madeNegative = -positivePeriod
@@ -192,6 +208,22 @@ class DateTimeTest extends GroovyTestCase {
         assert doublePeriod.days == 2
     }
 
+    void testPeriodIsPositiveIsNonnegativeIsNonpositive() {
+        def pos = Period.ofDays(10)
+        assert pos.isPositive() == true
+        assert pos.isNonpositive() == false
+        assert pos.isNonnegative() == true
+
+        def neg = Period.ofDays(-10)
+        assert neg.isPositive() == false
+        assert neg.isNonpositive() == true
+        assert neg.isNonnegative() == false
+
+        assert Period.ZERO.isPositive() == false
+        assert Period.ZERO.isNonpositive() == true
+        assert Period.ZERO.isNonnegative() == true
+    }
+
     void testTemporalGetAt() {
         def epoch = Instant.ofEpochMilli(0)
         assert epoch[ChronoField.INSTANT_SECONDS] == 0
@@ -235,50 +267,66 @@ class DateTimeTest extends GroovyTestCase {
         assert yearMonthPeriod.months == 2
     }
 
-    void testUptoDowntoWithSecondsDefaultUnit() {
+    void testUptoSelfWithDefaultUnit() {
         def epoch = Instant.ofEpochMilli(0)
 
-        int uptoSelfIterations = 0
+        int iterations = 0
         epoch.upto(epoch) {
-            ++uptoSelfIterations
-            assert it == epoch  : 'upto closure should be provided with arg'
+            ++iterations
+            assert it == epoch: 'upto closure should be provided with arg'
         }
-        assert uptoSelfIterations == 1 : 'Iterating upto same value should call closure once'
+        assert iterations == 1: 'Iterating upto same value should call closure once'
+    }
 
-        int downtoSelfIterations = 0
+    void testDowntoSelfWithDefaultUnit() {
+        def epoch = Instant.ofEpochMilli(0)
+        int iterations = 0
         epoch.downto(epoch) {
-            ++downtoSelfIterations
-            assert it == epoch : 'downto closure should be provided with arg'
+            ++iterations
+            assert it == epoch: 'downto closure should be provided with arg'
         }
-        assert downtoSelfIterations == 1 : 'Iterating downto same value should call closure once'
+        assert iterations == 1: 'Iterating downto same value should call closure once'
+    }
+
+    void testUptoWithSecondsDefaultUnit() {
+        def epoch = Instant.ofEpochMilli(0)
 
-        int uptoPlusOneIterations = 0
-        Instant endUp = null
+        int iterations = 0
+        Instant end = null
         epoch.upto(epoch + 1) {
-            ++uptoPlusOneIterations
-            endUp = it
+            ++iterations
+            end = it
         }
-        assert uptoPlusOneIterations == 2 : 'Iterating upto Temporal+1 value should call closure twice'
-        assert endUp.epochSecond == 1 : 'Unexpected upto final value'
+        assert iterations == 2: 'Iterating upto Temporal+1 value should call closure twice'
+        assert end.epochSecond == 1: 'Unexpected upto final value'
+    }
+
+    void testDowntoWithSecondsDefaultUnit() {
+        def epoch = Instant.ofEpochMilli(0)
 
-        int downtoPlusOneIterations = 0
-        Instant endDown = null
+        int iterations = 0
+        Instant end = null
         epoch.downto(epoch - 1) {
-            ++downtoPlusOneIterations
-            endDown = it
+            ++iterations
+            end = it
         }
-        assert downtoPlusOneIterations == 2 : 'Iterating downto Temporal+1 value should call closure twice'
-        assert endDown.epochSecond == -1 : 'Unexpected downto final value'
+        assert iterations == 2 : 'Iterating downto Temporal+1 value should call closure twice'
+        assert end.epochSecond == -1 : 'Unexpected downto final value'
     }
 
-    void testUptoDowntoWithYearsDefaultUnit() {
-        // non-ChronoUnit.SECOND iterations
+    void testUptoWithYearsDefaultUnit() {
         def endYear = null
         Year.of(1970).upto(Year.of(1971)) { year -> endYear = year }
         assert endYear.value == 1971
     }
 
-    void testUptoDownWithMonthsDefaultUnit() {
+    void testDowntoWithYearsDefaultUnit() {
+        def endYear = null
+        Year.of(1971).downto(Year.of(1970)) { year -> endYear = year }
+        assert endYear.value == 1970
+    }
+
+    void testUptoWithMonthsDefaultUnit() {
         def endYearMonth = null
         YearMonth.of(1970, Month.JANUARY).upto(YearMonth.of(1970, Month.FEBRUARY)) { yearMonth ->
             endYearMonth = yearMonth
@@ -286,21 +334,42 @@ class DateTimeTest extends GroovyTestCase {
         assert endYearMonth.month == Month.FEBRUARY
     }
 
-    void testUptoDowntoWithDaysDefaultUnit() {
+    void testDowntoWithMonthsDefaultUnit() {
+        def endYearMonth = null
+        YearMonth.of(1970, Month.FEBRUARY).downto(YearMonth.of(1970, Month.JANUARY)) { yearMonth ->
+            endYearMonth = yearMonth
+        }
+        assert endYearMonth.month == Month.JANUARY
+    }
+
+    void testUptoWithDaysDefaultUnit() {
         def endLocalDate = null
-        LocalDate.of(1970, Month.JANUARY, 1).upto(LocalDate.of(1970, Month.JANUARY, 2)) { localDate ->
+        LocalDate.of(1970, Month.JANUARY, 1).upto(LocalDate.of(1970, Month.JANUARY, 2)) {  localDate ->
             endLocalDate = localDate
         }
         assert endLocalDate.dayOfMonth == 2
     }
 
-    void testUptoDowntoWithIllegalReversedArguments() {
+    void testDowntoWithDaysDefaultUnit() {
+        def endLocalDate = null
+        LocalDate.of(1970, Month.JANUARY, 2).downto(LocalDate.of(1970, Month.JANUARY, 1)) {  localDate ->
+            endLocalDate = localDate
+        }
+        assert endLocalDate.dayOfMonth == 1
+    }
+
+    void testUptoWithIllegalReversedArguments() {
         def epoch = Instant.ofEpochMilli(0)
         try {
             epoch.upto(epoch - 1) {
                 fail('upto() should fail when passed earlier arg')
             }
-        } catch (GroovyRuntimeException e) {}
+        } catch (GroovyRuntimeException e) {
+        }
+    }
+
+    void testDowntoWithIllegalReversedArguments() {
+        def epoch = Instant.ofEpochMilli(0)
         try {
             epoch.downto(epoch + 1) {
                 fail('downto() should fail when passed earlier arg')
@@ -308,27 +377,56 @@ class DateTimeTest extends GroovyTestCase {
         } catch (GroovyRuntimeException e) {}
     }
 
-    void testUptoDowntoWithCustomUnit() {
-        LocalDateTime ldt1 = LocalDateTime.of(2018, Month.FEBRUARY, 11, 22, 9, 34)
-        LocalDateTime ldt2 = ldt1.plusMinutes(1)
+    void testUptoSelfWithCustomUnit() {
+        def today = LocalDate.now()
 
-        int upIterations = 0
-        LocalDateTime endUp = null
-        ldt1.upto(ldt2, ChronoUnit.DAYS) {
-            ++upIterations
-            endUp = it
+        int iterations = 0
+        today.upto(today, ChronoUnit.MONTHS) {
+            ++iterations
+            assert it == today: 'upto closure should be provided with arg'
         }
-        assert upIterations == 2
-        assert endUp.dayOfMonth == 12 : "Upto should have iterated by DAYS"
-
-        int downIterations = 0
-        LocalDateTime endDown = null
-        ldt2.downto(ldt1, ChronoUnit.YEARS) {
-            ++downIterations
-            endDown = it
+        assert iterations == 1: 'Iterating upto same value should call closure once'
+    }
+
+    void testDowntoSelfWithCustomUnit() {
+        def today = LocalDate.now()
+
+        int iterations = 0
+        today.downto(today, ChronoUnit.MONTHS) {
+            ++iterations
+            assert it == today: 'downto closure should be provided with arg'
         }
-        assert downIterations == 2
-        assert endDown.year == 2017 : "Downto should have iterated by YEARS"
+        assert iterations == 1: 'Iterating downto same value should call closure once'
+    }
+
+    void testUptoWithCustomUnit() {
+        LocalDateTime from = LocalDateTime.of(2018, Month.FEBRUARY, 11, 22, 9, 34)
+        // one second beyond one iteration
+        LocalDateTime to = from.plusDays(1).plusSeconds(1)
+
+        int iterations = 0
+        LocalDateTime end = null
+        from.upto(to, ChronoUnit.DAYS) {
+            ++iterations
+            end = it
+        }
+        assert iterations == 2
+        assert end.dayOfMonth == 12: "Upto should have iterated by DAYS twice"
+    }
+
+    void testDowntoWithCustomUnit() {
+        LocalDateTime from = LocalDateTime.of(2018, Month.FEBRUARY, 11, 22, 9, 34)
+        // one day beyond one iteration
+        LocalDateTime to = from.minusYears(1).minusDays(1)
+
+        int iterations = 0
+        LocalDateTime end = null
+        from.downto(to, ChronoUnit.YEARS) {
+            ++iterations
+            end = it
+        }
+        assert iterations == 2
+        assert end.year == 2017 : "Downto should have iterated by YEARS twice"
     }
 
     void testInstantToDateToCalendar() {
@@ -677,4 +775,19 @@ class DateTimeTest extends GroovyTestCase {
         assert [zdt.hour, zdt.minute, zdt.second] == [21, 43, 03]
         assert zdt.nano == 2 * 1e6
     }
+
+    void testPeriodBetweenYears() {
+        def period = Period.between(Year.of(2000), Year.of(2010))
+        assert period.years == 10
+        assert period.months == 0
+        assert period.days == 0
+    }
+
+    void testPeriodBetweenYearMonths() {
+        def period = Period.between(YearMonth.of(2018, Month.MARCH), YearMonth.of(2016, Month.APRIL))
+
+        assert period.years == -1
+        assert period.months == -11
+        assert period.days == 0
+    }
 }


[04/14] groovy git commit: initial port of Goodtimes-inspired methods

Posted by pa...@apache.org.
http://git-wip-us.apache.org/repos/asf/groovy/blob/99db9bf8/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
index e9ec3c0..55d3d07 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
@@ -26,11 +26,11 @@ import java.io.File;
 import java.io.IOException;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.ResourceBundle;
-import java.util.TimeZone;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
 import java.util.regex.Matcher;
+import java.util.stream.Collectors;
 
 /**
  * This class defines all the new static groovy methods which appear on normal
@@ -42,6 +42,7 @@ import java.util.regex.Matcher;
  * @author Joachim Baumann
  * @author Paul King
  * @author Kent Inge Fagerland Simonsen
+ * @author Joe Wolf
  */
 public class DefaultGroovyStaticMethods {
 
@@ -310,4 +311,169 @@ public class DefaultGroovyStaticMethods {
     return System.currentTimeMillis() / 1000;
   }
 
+    /**
+     * Parse text into a {@link java.time.LocalDate} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return a LocalDate representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.LocalDate#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static LocalDate parse(final LocalDate type, CharSequence text, String pattern) {
+        return LocalDate.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Parse text into a {@link java.time.LocalDateTime} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return a LocalDateTime representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.LocalDateTime#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static LocalDateTime parse(final LocalDateTime type, CharSequence text, String pattern) {
+        return LocalDateTime.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Parse text into a {@link java.time.LocalTime} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return a LocalTime representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.LocalTime#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static LocalTime parse(final LocalTime type, CharSequence text, String pattern) {
+        return LocalTime.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Parse text into a {@link java.time.MonthDay} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return a MonthDay representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.MonthDay#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static MonthDay parse(final MonthDay type, CharSequence text, String pattern) {
+        return MonthDay.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Parse text into an {@link java.time.OffsetDateTime} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return an OffsetDateTime representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.OffsetDateTime#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static OffsetDateTime parse(final OffsetDateTime type, CharSequence text, String pattern) {
+        return OffsetDateTime.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Parse text into an {@link java.time.OffsetTime} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return an OffsetTime representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.OffsetTime#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static OffsetTime parse(final OffsetTime type, CharSequence text, String pattern) {
+        return OffsetTime.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Parse text into a {@link java.time.Year} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return a Year representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.Year#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static Year parse(final Year type, CharSequence text, String pattern) {
+        return Year.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Parse text into a {@link java.time.YearMonth} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return a YearMonth representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.YearMonth#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static YearMonth parse(final YearMonth type, CharSequence text, String pattern) {
+        return YearMonth.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Parse text into a {@link java.time.ZonedDateTime} using the provided pattern.
+     *
+     * @param type    placeholder variable used by Groovy categories; ignored for default static methods
+     * @param text    String to be parsed to create the date instance
+     * @param pattern pattern used to parse the text
+     * @return a ZonedDateTime representing the parsed text
+     * @throws java.lang.IllegalArgumentException if the pattern is invalid
+     * @throws java.time.format.DateTimeParseException if the text cannot be parsed
+     * @see java.time.format.DateTimeFormatter
+     * @see java.time.ZonedDateTime#parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)
+     * @since 3.0
+     */
+    public static ZonedDateTime parse(final ZonedDateTime type, CharSequence text, String pattern) {
+        return ZonedDateTime.parse(text, DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Returns the {@link java.time.ZoneOffset} currently associated with the system default {@link java.time.ZoneId}.
+     *
+     * @param type placeholder variable used by Groovy categories; ignored for default static methods
+     * @return a ZoneOffset
+     * @see java.time.ZoneId#systemDefault()
+     * @since 3.0
+     */
+    public static ZoneOffset systemDefault(final ZoneOffset type) {
+        return DateTimeGroovyMethods.getOffset(ZoneId.systemDefault());
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/99db9bf8/src/test/groovy/DateTimeTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/groovy/DateTimeTest.groovy b/src/test/groovy/DateTimeTest.groovy
new file mode 100644
index 0000000..088f460
--- /dev/null
+++ b/src/test/groovy/DateTimeTest.groovy
@@ -0,0 +1,680 @@
+package groovy
+
+import java.text.SimpleDateFormat
+import java.time.*
+import java.time.temporal.ChronoField
+import java.time.temporal.ChronoUnit
+
+class DateTimeTest extends GroovyTestCase {
+
+    void testDurationPlusMinusPositiveNegative() {
+        def duration = Duration.ofSeconds(10)
+        def longer = duration + 5
+        def shorter = duration - 5
+
+        assert longer.seconds == 15
+        assert shorter.seconds == 5
+        assert (++longer).seconds == 16
+        assert (--shorter).seconds == 4
+    }
+
+    void testInstantPlusMinusPositiveNegative() {
+        def epoch = Instant.ofEpochMilli(0)
+
+        def twoSecPastEpoch = epoch + 2
+        def oneSecPastEpoch = twoSecPastEpoch - 1
+
+        assert oneSecPastEpoch.epochSecond == 1
+        assert twoSecPastEpoch.epochSecond == 2
+        assert (++twoSecPastEpoch).epochSecond == 3
+        assert (--oneSecPastEpoch).epochSecond == 0
+    }
+
+    void testLocalDatePlusMinusPositiveNegative() {
+        def epoch = LocalDate.of(1970, Month.JANUARY, 1)
+
+        def twoDaysPastEpoch = epoch + 2
+        def oneDayPastEpoch = twoDaysPastEpoch - 1
+
+        assert oneDayPastEpoch.dayOfMonth == 2
+        assert twoDaysPastEpoch.dayOfMonth == 3
+        assert (++twoDaysPastEpoch).dayOfMonth == 4
+        assert (--oneDayPastEpoch).dayOfMonth == 1
+    }
+
+    void testLocalDateTimePlusMinusPositiveNegative() {
+        def epoch = LocalDateTime.of(1970, Month.JANUARY, 1, 0, 0, 0, 0)
+
+        def twoSecsPastEpoch = epoch + 2
+        def oneSecPastEpoch = twoSecsPastEpoch - 1
+
+        assert oneSecPastEpoch.second == 1
+        assert twoSecsPastEpoch.second == 2
+        assert (++twoSecsPastEpoch).second == 3
+        assert (--oneSecPastEpoch).second == 0
+    }
+
+    void testLocalTimePlusMinusPositiveNegative() {
+        def epoch = LocalTime.of(0, 0, 0, 0)
+
+        def twoSecsPastEpoch = epoch + 2
+        def oneSecPastEpoch = twoSecsPastEpoch - 1
+
+        assert oneSecPastEpoch.second == 1
+        assert twoSecsPastEpoch.second == 2
+        assert (++twoSecsPastEpoch).second == 3
+        assert (--oneSecPastEpoch).second == 0
+    }
+
+    void testOffsetDateTimePlusMinusPositiveNegative() {
+        def epoch = OffsetDateTime.of(LocalDateTime.of(1970, Month.JANUARY, 1, 0, 0, 0, 0),
+                ZoneOffset.ofHours(0))
+
+        def twoSecsPastEpoch = epoch + 2
+        def oneSecPastEpoch = twoSecsPastEpoch - 1
+
+        assert oneSecPastEpoch.second == 1
+        assert twoSecsPastEpoch.second == 2
+        assert (++twoSecsPastEpoch).second == 3
+        assert (--oneSecPastEpoch).second == 0
+    }
+
+    void testOffsetTimePlusMinusPositiveNegative() {
+        def epoch = OffsetTime.of(LocalTime.of(0, 0, 0, 0),
+                ZoneOffset.ofHours(0))
+
+        def twoSecsPastEpoch = epoch + 2
+        def oneSecPastEpoch = twoSecsPastEpoch - 1
+
+        assert oneSecPastEpoch.second == 1
+        assert twoSecsPastEpoch.second == 2
+        assert (++twoSecsPastEpoch).second == 3
+        assert (--oneSecPastEpoch).second == 0
+    }
+
+    void testPeriodPlusMinusPositiveNegative() {
+        def fortnight = Period.ofDays(14)
+
+        def fortnightAndTwoDays = fortnight + 2
+        def fortnightAndOneDay = fortnightAndTwoDays - 1
+
+        assert fortnightAndOneDay.days == 15
+        assert fortnightAndTwoDays.days == 16
+        assert (++fortnightAndTwoDays).days == 17
+        assert (--fortnightAndOneDay).days == 14
+    }
+
+    void testYearPlusMinusPositiveNegative() {
+        def epoch = Year.of(1970)
+
+        def twoYearsAfterEpoch = epoch + 2
+        def oneYearAfterEpoch = twoYearsAfterEpoch - 1
+
+        assert oneYearAfterEpoch.value == 1971
+        assert twoYearsAfterEpoch.value == 1972
+        assert (++twoYearsAfterEpoch).value == 1973
+        assert (--oneYearAfterEpoch).value == 1970
+    }
+
+    void testYearMonthPlusMinusPositiveNegative() {
+        def epoch = YearMonth.of(1970, Month.JANUARY)
+
+        def twoMonthsAfterEpoch = epoch + 2
+        def oneMonthAfterEpoch = twoMonthsAfterEpoch - 1
+
+        assert oneMonthAfterEpoch.month == Month.FEBRUARY
+        assert twoMonthsAfterEpoch.month == Month.MARCH
+        assert (++twoMonthsAfterEpoch).month == Month.APRIL
+        assert (--oneMonthAfterEpoch).month == Month.JANUARY
+    }
+
+    void testZonedDateTimePlusMinusPositiveNegative() {
+        def epoch = ZonedDateTime.of(LocalDateTime.of(1970, Month.JANUARY, 1, 0, 0, 0, 0),
+                ZoneId.systemDefault())
+
+        def twoSecsPastEpoch = epoch + 2
+        def oneSecPastEpoch = twoSecsPastEpoch - 1
+
+        assert oneSecPastEpoch.second == 1
+        assert twoSecsPastEpoch.second == 2
+        assert (++twoSecsPastEpoch).second == 3
+        assert (--oneSecPastEpoch).second == 0
+    }
+
+    void testDayOfWeekPlusMinus() {
+        def mon = DayOfWeek.MONDAY
+
+        assert mon + 4 == DayOfWeek.FRIDAY
+        assert mon - 4 == DayOfWeek.THURSDAY
+    }
+
+    void testMonthPlusMinus() {
+        def jan = Month.JANUARY
+
+        assert jan + 4 == Month.MAY
+        assert jan - 4 == Month.SEPTEMBER
+    }
+
+    void testDurationPositiveNegative() {
+        def positiveDuration = Duration.ofSeconds(3)
+        assert (-positiveDuration).seconds == -3
+
+        def negativeDuration = Duration.ofSeconds(-5)
+        assert (+negativeDuration).seconds == 5
+    }
+
+    void testDurationMultiplyDivide() {
+        def duration = Duration.ofSeconds(60)
+
+        assert (duration / 2).seconds == 30
+        assert (duration * 2).seconds == 120
+    }
+
+    void testPeriodPositiveNegative() {
+        def positivePeriod = Period.of(1,2,3)
+        Period madeNegative = -positivePeriod
+        assert madeNegative.years == -1 : "All Period fields should be made negative"
+        assert madeNegative.months == -2
+        assert madeNegative.days == -3
+
+        def negativePeriod = Period.of(-1,2,-3)
+        Period madePositive = +negativePeriod
+        assert madePositive.years == 1 : "Negative Period fields should be made positive"
+        assert madePositive.months == 2 : "Positive Period fields should remain positive"
+        assert madePositive.days == 3
+    }
+
+    void testPeriodMultiply() {
+        def period = Period.of(1,1,1)
+        Period doublePeriod = period * 2
+        assert doublePeriod.years == 2
+        assert doublePeriod.months == 2
+        assert doublePeriod.days == 2
+    }
+
+    void testTemporalGetAt() {
+        def epoch = Instant.ofEpochMilli(0)
+        assert epoch[ChronoField.INSTANT_SECONDS] == 0
+    }
+
+    void testTemporalAmountGetAt() {
+        def duration = Duration.ofHours(10)
+        assert duration[ChronoUnit.SECONDS] == 36_000
+    }
+
+    void testZoneOffsetGetAt() {
+        def offset = ZoneOffset.ofTotalSeconds(360)
+        assert offset[ChronoField.OFFSET_SECONDS] == 360
+    }
+
+    void testTemporalRightShift() {
+        def epoch = Instant.ofEpochMilli(0)
+        def dayAfterEpoch = epoch + (60 * 60 * 24)
+        Duration instantDuration = epoch >> dayAfterEpoch
+        assert instantDuration == Duration.ofDays(1)
+    }
+
+    void testLocalDateRightShift() {
+        def localDate1 = LocalDate.of(2000, Month.JANUARY, 1)
+        def localDate2 = localDate1.plusYears(2)
+        Period localDatePeriod = localDate1 >> localDate2
+        assert localDatePeriod.years == 2
+    }
+
+    void testYearRightShift() {
+        def year1 = Year.of(2000)
+        def year2 = Year.of(2018)
+        Period yearPeriod = year1 >> year2
+        assert yearPeriod.years == 18
+    }
+
+    void testYearMonthRightShift() {
+        def yearMonth1 = YearMonth.of(2018, Month.JANUARY)
+        def yearMonth2 = YearMonth.of(2018, Month.MARCH)
+        Period yearMonthPeriod = yearMonth1 >> yearMonth2
+        assert yearMonthPeriod.months == 2
+    }
+
+    void testUptoDowntoWithSecondsDefaultUnit() {
+        def epoch = Instant.ofEpochMilli(0)
+
+        int uptoSelfIterations = 0
+        epoch.upto(epoch) {
+            ++uptoSelfIterations
+            assert it == epoch  : 'upto closure should be provided with arg'
+        }
+        assert uptoSelfIterations == 1 : 'Iterating upto same value should call closure once'
+
+        int downtoSelfIterations = 0
+        epoch.downto(epoch) {
+            ++downtoSelfIterations
+            assert it == epoch : 'downto closure should be provided with arg'
+        }
+        assert downtoSelfIterations == 1 : 'Iterating downto same value should call closure once'
+
+        int uptoPlusOneIterations = 0
+        Instant endUp = null
+        epoch.upto(epoch + 1) {
+            ++uptoPlusOneIterations
+            endUp = it
+        }
+        assert uptoPlusOneIterations == 2 : 'Iterating upto Temporal+1 value should call closure twice'
+        assert endUp.epochSecond == 1 : 'Unexpected upto final value'
+
+        int downtoPlusOneIterations = 0
+        Instant endDown = null
+        epoch.downto(epoch - 1) {
+            ++downtoPlusOneIterations
+            endDown = it
+        }
+        assert downtoPlusOneIterations == 2 : 'Iterating downto Temporal+1 value should call closure twice'
+        assert endDown.epochSecond == -1 : 'Unexpected downto final value'
+    }
+
+    void testUptoDowntoWithYearsDefaultUnit() {
+        // non-ChronoUnit.SECOND iterations
+        def endYear = null
+        Year.of(1970).upto(Year.of(1971)) { year -> endYear = year }
+        assert endYear.value == 1971
+    }
+
+    void testUptoDownWithMonthsDefaultUnit() {
+        def endYearMonth = null
+        YearMonth.of(1970, Month.JANUARY).upto(YearMonth.of(1970, Month.FEBRUARY)) { yearMonth ->
+            endYearMonth = yearMonth
+        }
+        assert endYearMonth.month == Month.FEBRUARY
+    }
+
+    void testUptoDowntoWithDaysDefaultUnit() {
+        def endLocalDate = null
+        LocalDate.of(1970, Month.JANUARY, 1).upto(LocalDate.of(1970, Month.JANUARY, 2)) { localDate ->
+            endLocalDate = localDate
+        }
+        assert endLocalDate.dayOfMonth == 2
+    }
+
+    void testUptoDowntoWithIllegalReversedArguments() {
+        def epoch = Instant.ofEpochMilli(0)
+        try {
+            epoch.upto(epoch - 1) {
+                fail('upto() should fail when passed earlier arg')
+            }
+        } catch (GroovyRuntimeException e) {}
+        try {
+            epoch.downto(epoch + 1) {
+                fail('downto() should fail when passed earlier arg')
+            }
+        } catch (GroovyRuntimeException e) {}
+    }
+
+    void testUptoDowntoWithCustomUnit() {
+        LocalDateTime ldt1 = LocalDateTime.of(2018, Month.FEBRUARY, 11, 22, 9, 34)
+        LocalDateTime ldt2 = ldt1.plusMinutes(1)
+
+        int upIterations = 0
+        LocalDateTime endUp = null
+        ldt1.upto(ldt2, ChronoUnit.DAYS) {
+            ++upIterations
+            endUp = it
+        }
+        assert upIterations == 2
+        assert endUp.dayOfMonth == 12 : "Upto should have iterated by DAYS"
+
+        int downIterations = 0
+        LocalDateTime endDown = null
+        ldt2.downto(ldt1, ChronoUnit.YEARS) {
+            ++downIterations
+            endDown = it
+        }
+        assert downIterations == 2
+        assert endDown.year == 2017 : "Downto should have iterated by YEARS"
+    }
+
+    void testInstantToDateToCalendar() {
+        def epoch = Instant.ofEpochMilli(0).plusNanos(999_999)
+
+        def date = epoch.toDate()
+        def cal = epoch.toCalendar()
+        assert cal.time == date
+        def sdf = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss.SSS')
+        sdf.timeZone = TimeZone.getTimeZone('GMT')
+        assert sdf.format(date) == '1970-01-01 00:00:00.000'
+    }
+
+    void testLocalDateToDateToCalendar() {
+        def ld = LocalDate.of(2018, Month.FEBRUARY, 12)
+
+        Calendar cal = ld.toCalendar()
+        assert cal.get(Calendar.YEAR) == 2018
+        assert cal.get(Calendar.MONTH) == Calendar.FEBRUARY
+        assert cal.get(Calendar.DAY_OF_MONTH) == 12
+        assert cal.timeZone.getID() == TimeZone.default.getID()
+
+        Date date = ld.toDate()
+        assert date.format('yyyy-MM-dd') == '2018-02-12'
+    }
+
+    void testLocalDateTimeToDateToCalendar() {
+        def ldt = LocalDateTime.of(2018, Month.FEBRUARY, 12, 22, 26, 30, 123_999_999)
+
+        Calendar cal = ldt.toCalendar()
+        assert cal.get(Calendar.YEAR) == 2018
+        assert cal.get(Calendar.MONTH) == Calendar.FEBRUARY
+        assert cal.get(Calendar.DAY_OF_MONTH) == 12
+        assert cal.get(Calendar.HOUR_OF_DAY) == 22
+        assert cal.get(Calendar.MINUTE) == 26
+        assert cal.get(Calendar.SECOND) == 30
+        assert cal.get(Calendar.MILLISECOND) == 123
+        assert cal.timeZone.getID() == TimeZone.default.getID()
+
+        Date date = ldt.toDate()
+        assert date.format('yyyy-MM-dd HH:mm:ss.SSS') == '2018-02-12 22:26:30.123'
+    }
+
+    void testLocalTimeToDateToCalendar() {
+        def today = Calendar.instance
+        def lt = LocalTime.of(22, 38, 20, 9_999_999)
+
+        Calendar cal = lt.toCalendar()
+        assert cal.get(Calendar.YEAR) == today.get(Calendar.YEAR) : 'LocalTime.toCalendar() should have current year'
+        assert cal.get(Calendar.MONTH) == today.get(Calendar.MONTH) : 'LocalTime.toCalendar() should have current month'
+        assert cal.get(Calendar.DAY_OF_MONTH) == today.get(Calendar.DAY_OF_MONTH) : 'LocalTime.toCalendar() should have current day'
+        assert cal.get(Calendar.HOUR_OF_DAY) == 22
+        assert cal.get(Calendar.MINUTE) == 38
+        assert cal.get(Calendar.SECOND) == 20
+        assert cal.get(Calendar.MILLISECOND) == 9
+        assert cal.timeZone.getID() == TimeZone.default.getID()
+
+        Date date = lt.toDate()
+        assert date.format('HH:mm:ss.SSS') == '22:38:20.009'
+    }
+
+    void testOffsetDateTimeToDateToCalendar() {
+        def ld = LocalDate.of(2018, Month.FEBRUARY, 12)
+        def lt = LocalTime.of(22, 46, 10, 16_000_001)
+        def offset = ZoneOffset.ofHours(-5)
+        def odt = OffsetDateTime.of(ld, lt, offset)
+
+        Calendar cal = odt.toCalendar()
+        assert cal.get(Calendar.YEAR) == 2018
+        assert cal.get(Calendar.MONTH) == Calendar.FEBRUARY
+        assert cal.get(Calendar.DAY_OF_MONTH) == 12
+        assert cal.get(Calendar.HOUR_OF_DAY) == 22
+        assert cal.get(Calendar.MINUTE) == 46
+        assert cal.get(Calendar.SECOND) == 10
+        assert cal.get(Calendar.MILLISECOND) == 16
+        assert cal.timeZone.getOffset(System.currentTimeMillis()) == -5 * 60 * 60 * 1000
+
+        Date date = odt.toDate()
+        def sdf = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss.SSS Z')
+        sdf.timeZone = cal.timeZone
+        assert sdf.format(date) == '2018-02-12 22:46:10.016 -0500'
+    }
+
+    void testOffsetTimeToDateToCalendar() {
+        def lt = LocalTime.of(22, 53, 2, 909_900_009)
+        def offset = ZoneOffset.ofHours(-4)
+        def ot = OffsetTime.of(lt, offset)
+        Calendar today = Calendar.getInstance(TimeZone.getTimeZone('GMT-4'))
+
+        Calendar cal = ot.toCalendar()
+        assert cal.get(Calendar.YEAR) == today.get(Calendar.YEAR) : 'OffsetTime.toCalendar() should have current year'
+        assert cal.get(Calendar.MONTH) == today.get(Calendar.MONTH) : 'OffsetTime.toCalendar() should have current month'
+        assert cal.get(Calendar.DAY_OF_MONTH) == today.get(Calendar.DAY_OF_MONTH) : 'OffsetTime.toCalendar() should have current day'
+        assert cal.get(Calendar.HOUR_OF_DAY) == 22
+        assert cal.get(Calendar.MINUTE) == 53
+        assert cal.get(Calendar.SECOND) == 2
+        assert cal.get(Calendar.MILLISECOND) == 909
+        assert cal.timeZone.getOffset(System.currentTimeMillis()) == -4 * 60 * 60 * 1000
+
+        Date date = ot.toDate()
+        def sdf = new SimpleDateFormat('HH:mm:ss.SSS Z')
+        sdf.timeZone = cal.timeZone
+        assert sdf.format(date) == '22:53:02.909 -0400'
+    }
+
+    void testZonedDateTimeToDateToCalendar() {
+        def ldt = LocalDateTime.of(2018, Month.FEBRUARY, 13, 20, 33, 57)
+        def zoneId = ZoneId.ofOffset('GMT', ZoneOffset.ofHours(3))
+        def zdt = ZonedDateTime.of(ldt, zoneId)
+
+        Calendar cal = zdt.toCalendar()
+        assert cal.get(Calendar.YEAR) == 2018
+        assert cal.get(Calendar.MONTH) == Calendar.FEBRUARY
+        assert cal.get(Calendar.DAY_OF_MONTH) == 13
+        assert cal.get(Calendar.HOUR_OF_DAY) == 20
+        assert cal.get(Calendar.MINUTE) == 33
+        assert cal.get(Calendar.SECOND) == 57
+        assert cal.get(Calendar.MILLISECOND) == 0
+        assert cal.timeZone.getOffset(System.currentTimeMillis()) == 3 * 60 * 60 * 1000
+
+        Date date = zdt.toDate()
+        def sdf = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss.SSS Z')
+        sdf.timeZone = cal.timeZone
+        assert sdf.format(date) == '2018-02-13 20:33:57.000 +0300'
+    }
+
+    void testZoneOffsetExtensionProperties() {
+        def offset = ZoneOffset.ofHoursMinutesSeconds(3,4,5)
+        assert offset.hours == 3
+        assert offset.minutes == 4
+        assert offset.seconds == 5
+
+        def negOffset = ZoneOffset.ofHoursMinutesSeconds(-1, -2, -3)
+        assert negOffset.hours == -1
+        assert negOffset.minutes == -2
+        assert negOffset.seconds == -3
+    }
+
+    void testZoneOffsetToZimeZone() {
+        TimeZone utcTz = ZoneOffset.UTC.toTimeZone()
+        assert utcTz.getID() == 'GMT'
+
+        TimeZone noSecsTz = ZoneOffset.ofHoursMinutes(1, 30).toTimeZone()
+        assert noSecsTz.getID() == 'GMT+01:30'
+
+        TimeZone secsTz = ZoneOffset.ofHoursMinutesSeconds(-4, -15, -30).toTimeZone()
+        assert secsTz.getID() == 'GMT-04:15'
+    }
+
+    void testZoneIdExtensionProperties() {
+        def offset = ZoneOffset.ofHours(7)
+        def zoneId = ZoneId.ofOffset('GMT', offset)
+
+        assert zoneId.offset.totalSeconds == offset.totalSeconds
+        assert zoneId.getOffset(Instant.now()).totalSeconds == offset.totalSeconds
+        assert zoneId.shortName == 'GMT+07:00'
+        assert zoneId.fullName == 'GMT+07:00'
+
+        ZoneId ny = ZoneId.of('America/New_York')
+        assert ny.getShortName(Locale.US) == 'ET'
+        assert ny.getFullName(Locale.US) == 'Eastern Time'
+    }
+
+    void testZoneIdToTimeZone() {
+        ZoneId ny = ZoneId.of('America/New_York')
+
+        assert ny.toTimeZone() == TimeZone.getTimeZone(ny)
+    }
+
+    void testYearExtensionProperties() {
+        def year = Year.of(2009)
+        assert year.era == 1
+        assert year.yearOfEra == 2009
+    }
+
+    void testDayOfWeekExtensionProperties() {
+        assert DayOfWeek.SUNDAY.weekend
+        assert DayOfWeek.MONDAY.weekday
+    }
+
+    void testYear_Month_leftShift() {
+        def a = Year.now()
+        def b = Month.JULY
+
+        YearMonth x = a << b
+        YearMonth y = b << a
+        assert x == y
+    }
+
+    void testYear_MonthDay_leftShift() {
+        def a = Year.now()
+        def b = MonthDay.now()
+
+        LocalDate x = a << b
+        LocalDate y = b << a
+        assert x == y
+    }
+
+    void testMonthDay_leftShift() {
+        LocalDate d = MonthDay.of(Month.FEBRUARY, 13) << 2018
+        assert d.year == 2018
+        assert d.month == Month.FEBRUARY
+        assert d.dayOfMonth == 13
+    }
+
+    void testMonth_leftShift() {
+        MonthDay md = Month.JANUARY << 10
+        assert md.month == Month.JANUARY
+        assert md.dayOfMonth == 10
+    }
+
+    void testLocalDate_LocalTime_leftShift() {
+        def a = LocalDate.now()
+        def b = LocalTime.now()
+
+        LocalDateTime x = a << b
+        LocalDateTime y = b << a
+        assert x == y
+    }
+
+    void testLocalDate_OffsetTime_leftShift() {
+        def a = LocalDate.now()
+        def b = OffsetTime.now()
+
+        OffsetDateTime x = a << b
+        OffsetDateTime y = b << a
+        assert x == y
+    }
+
+    void testLocalDateTime_ZoneOffset_leftShift() {
+        def a = LocalDateTime.now()
+        def b = ZoneOffset.ofHours(5)
+
+        OffsetDateTime x = a << b
+        OffsetDateTime y = b << a
+        assert x == y
+    }
+
+    void testLocalDateTime_ZoneId_leftShift() {
+        def a = LocalDateTime.now()
+        def b = ZoneId.systemDefault()
+
+        ZonedDateTime x = a << b
+        ZonedDateTime y = b << a
+        assert x == y
+    }
+
+    void testLocalTime_ZoneOffset_leftShift() {
+        def a = LocalTime.now()
+        def b = ZoneOffset.ofHours(5)
+
+        OffsetTime x = a << b
+        OffsetTime y = b << a
+        assert x == y
+    }
+
+    void testLocalDateTimeClearTime() {
+        def d = LocalDateTime.of(LocalDate.now(), LocalTime.of(8, 9, 10, 100_032))
+        d = d.clearTime()
+
+        assert d.hour == 0
+        assert d.minute == 0
+        assert d.second == 0
+        assert d.nano == 0
+    }
+
+    void testOffsetDateTimeClearTime() {
+        def offset = ZoneOffset.ofHours(-1)
+        def d = OffsetDateTime.of(LocalDate.now(), LocalTime.of(8, 9, 10, 100_032), offset)
+        d = d.clearTime()
+
+        assert d.hour == 0
+        assert d.minute == 0
+        assert d.second == 0
+        assert d.nano == 0
+        assert d.offset == offset : 'cleartTime() should not change offset'
+    }
+
+    void testZonedDateTimeClearTime() {
+        def zone =  ZoneId.of('America/New_York')
+        def d = ZonedDateTime.of(LocalDate.now(), LocalTime.of(8, 9, 10, 100_032), zone)
+        d = d.clearTime()
+
+        assert d.hour == 0
+        assert d.minute == 0
+        assert d.second == 0
+        assert d.nano == 0
+        assert d.zone == zone : 'cleartTime() should not change zone'
+    }
+
+    void testFormatByPattern() {
+        def zone =  ZoneId.of('America/New_York')
+        def offset = ZoneOffset.ofHours(2)
+
+        LocalDate ld = LocalDate.of(2018, Month.FEBRUARY, 13)
+        LocalTime lt = LocalTime.of(3,4,5,6_000_000)
+        LocalDateTime ldt = LocalDateTime.of(ld, lt)
+        OffsetTime ot = OffsetTime.of(lt, offset)
+        OffsetDateTime odt = OffsetDateTime.of(ldt, offset)
+        ZonedDateTime zdt = ZonedDateTime.of(ldt, zone)
+
+        assert ld.format('yyyy-MM-dd') == '2018-02-13'
+        assert lt.format('HH:mm:ss.SSS') == '03:04:05.006'
+        assert ldt.format('yyyy-MM-dd HH:mm:ss.SSS') == '2018-02-13 03:04:05.006'
+        assert ot.format('HH:mm:ss.SSS Z') == '03:04:05.006 +0200'
+        assert odt.format('yyyy-MM-dd HH:mm:ss.SSS Z') == '2018-02-13 03:04:05.006 +0200'
+        assert zdt.format('yyyy-MM-dd HH:mm:ss.SSS VV') == '2018-02-13 03:04:05.006 America/New_York'
+    }
+
+    void testLocalDateParse() {
+        LocalDate ld = LocalDate.parse('2018-02-15', 'yyyy-MM-dd')
+        assert [ld.year, ld.month, ld.dayOfMonth] == [2018, Month.FEBRUARY, 15]
+    }
+
+    void testLocalDateTimeParse() {
+        LocalDateTime ldt = LocalDateTime.parse('2018-02-15 21:43:03.002', 'yyyy-MM-dd HH:mm:ss.SSS')
+        assert [ldt.year, ldt.month, ldt.dayOfMonth] == [2018, Month.FEBRUARY, 15]
+        assert [ldt.hour, ldt.minute, ldt.second] == [21, 43, 03]
+        assert ldt.nano == 2 * 1e6
+    }
+
+    void testLocalTimeParse() {
+        LocalTime lt = LocalTime.parse('21:43:03.002', 'HH:mm:ss.SSS')
+        assert [lt.hour, lt.minute, lt.second] == [21, 43, 03]
+        assert lt.nano == 2 * 1e6
+    }
+
+    void testOffsetDateTimeParse() {
+        OffsetDateTime odt = OffsetDateTime.parse('2018-02-15 21:43:03.002 -00', 'yyyy-MM-dd HH:mm:ss.SSS X')
+        assert [odt.year, odt.month, odt.dayOfMonth] == [2018, Month.FEBRUARY, 15]
+        assert [odt.hour, odt.minute, odt.second] == [21, 43, 03]
+        assert odt.nano == 2 * 1e6
+        assert odt.offset.totalSeconds == 0
+    }
+
+    void testOffsetTimeParse() {
+        OffsetTime ot = OffsetTime.parse('21:43:03.002 -00', 'HH:mm:ss.SSS X')
+        assert [ot.hour, ot.minute, ot.second] == [21, 43, 03]
+        assert ot.nano == 2 * 1e6
+        assert ot.offset.totalSeconds == 0
+    }
+
+    void testZonedDateTimeParse() {
+        ZonedDateTime zdt = ZonedDateTime.parse('2018-02-15 21:43:03.002 UTC', 'yyyy-MM-dd HH:mm:ss.SSS z')
+        assert [zdt.year, zdt.month, zdt.dayOfMonth] == [2018, Month.FEBRUARY, 15]
+        assert [zdt.hour, zdt.minute, zdt.second] == [21, 43, 03]
+        assert zdt.nano == 2 * 1e6
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/99db9bf8/src/test/java/org/codehaus/groovy/runtime/DateGroovyMethodsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/codehaus/groovy/runtime/DateGroovyMethodsTest.java b/src/test/java/org/codehaus/groovy/runtime/DateGroovyMethodsTest.java
index c34ec60..c182b12 100644
--- a/src/test/java/org/codehaus/groovy/runtime/DateGroovyMethodsTest.java
+++ b/src/test/java/org/codehaus/groovy/runtime/DateGroovyMethodsTest.java
@@ -24,7 +24,10 @@ import org.junit.Test;
 import java.sql.Timestamp;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.time.*;
 import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
 
 import static org.junit.Assert.assertEquals;
 
@@ -56,4 +59,58 @@ public class DateGroovyMethodsTest {
         calendar.setTime(sdf.parse("20180101"));
         assertEquals("20171231", sdf.format(DateGroovyMethods.previous(calendar).getTime()));
     }
+
+    @Test
+    public void calendarConversionsDefaultTimeZone() throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HHmmss SSS");
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(sdf.parse("20180115 153256 001"));
+
+        LocalDate expectedLocalDate = LocalDate.of(2018, Month.JANUARY, 15);
+        LocalTime expectedLocalTime = LocalTime.of(15, 32, 56, 1_000_000);
+        LocalDateTime expectedLocalDateTime = LocalDateTime.of(expectedLocalDate, expectedLocalTime);
+
+        assertEquals("DayOfWeek", DayOfWeek.MONDAY, DateGroovyMethods.toDayOfWeek(calendar));
+        assertEquals("Month", Month.JANUARY, DateGroovyMethods.toMonth(calendar));
+        assertEquals("MonthDay", MonthDay.of(Month.JANUARY, 15), DateGroovyMethods.toMonthDay(calendar));
+        assertEquals("YearMonth", YearMonth.of(2018, Month.JANUARY), DateGroovyMethods.toYearMonth(calendar));
+        assertEquals("Year", Year.of(2018), DateGroovyMethods.toYear(calendar));
+        assertEquals("LocalDate", expectedLocalDate, DateGroovyMethods.toLocalDate(calendar));
+        assertEquals("LocalTime", expectedLocalTime, DateGroovyMethods.toLocalTime(calendar));
+        assertEquals("LocalDateTime", expectedLocalDateTime, DateGroovyMethods.toLocalDateTime(calendar));
+        assertEquals("OffsetTime", expectedLocalTime, DateGroovyMethods.toOffsetTime(calendar).toLocalTime());
+        assertEquals("OffsetDateTime", expectedLocalDateTime,
+                DateGroovyMethods.toOffsetDateTime(calendar).toLocalDateTime());
+        assertEquals("ZonedDateTime", expectedLocalDateTime,
+                DateGroovyMethods.toZonedDateTime(calendar).toLocalDateTime());
+    }
+
+    @Test
+    public void calendarConversionsDifferingTimeZones() throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HHmmss SSS");
+        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC+0"));
+        calendar.setTime(sdf.parse("20180115 153256 001"));
+    }
+
+    @Test
+    public void sameCalendarAndDateConvertIdentically() throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HHmmss SSS");
+        Date date = sdf.parse("20180115 153256 001");
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+
+        assertEquals("DayOfWeek", DateGroovyMethods.toDayOfWeek(calendar), DateGroovyMethods.toDayOfWeek(date));
+        assertEquals("Month", DateGroovyMethods.toMonth(calendar), DateGroovyMethods.toMonth(date));
+        assertEquals("MonthDay", DateGroovyMethods.toMonthDay(calendar), DateGroovyMethods.toMonthDay(date));
+        assertEquals("YearMonth", DateGroovyMethods.toYearMonth(calendar), DateGroovyMethods.toYearMonth(date));
+        assertEquals("Year", DateGroovyMethods.toYear(calendar), DateGroovyMethods.toYear(date));
+        assertEquals("LocalDate", DateGroovyMethods.toLocalDate(calendar), DateGroovyMethods.toLocalDate(date));
+        assertEquals("LocalTime", DateGroovyMethods.toLocalTime(calendar), DateGroovyMethods.toLocalTime(date));
+        assertEquals("LocalDateTime", DateGroovyMethods.toLocalDate(calendar), DateGroovyMethods.toLocalDate(date));
+        assertEquals("OffsetTime", DateGroovyMethods.toOffsetTime(calendar), DateGroovyMethods.toOffsetTime(date));
+        assertEquals("OffsetDateTime",
+                DateGroovyMethods.toOffsetDateTime(calendar), DateGroovyMethods.toOffsetDateTime(date));
+        assertEquals("ZonedDateTime",
+                DateGroovyMethods.toZonedDateTime(calendar), DateGroovyMethods.toZonedDateTime(date));
+    }
 }


[10/14] groovy git commit: move datetime extensions to their own module

Posted by pa...@apache.org.
http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/subprojects/groovy-datetime/src/main/java/org/apache/groovy/datetime/extensions/DateTimeExtensions.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-datetime/src/main/java/org/apache/groovy/datetime/extensions/DateTimeExtensions.java b/subprojects/groovy-datetime/src/main/java/org/apache/groovy/datetime/extensions/DateTimeExtensions.java
new file mode 100644
index 0000000..9ba6014
--- /dev/null
+++ b/subprojects/groovy-datetime/src/main/java/org/apache/groovy/datetime/extensions/DateTimeExtensions.java
@@ -0,0 +1,2419 @@
+/*
+ *  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.
+ */
+package org.apache.groovy.datetime.extensions;
+
+import groovy.lang.Closure;
+import groovy.lang.GroovyRuntimeException;
+
+import java.time.DateTimeException;
+import java.time.DayOfWeek;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Month;
+import java.time.MonthDay;
+import java.time.OffsetDateTime;
+import java.time.OffsetTime;
+import java.time.Period;
+import java.time.Year;
+import java.time.YearMonth;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.chrono.ChronoLocalDate;
+import java.time.chrono.ChronoPeriod;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
+import java.time.format.TextStyle;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalAmount;
+import java.time.temporal.TemporalField;
+import java.time.temporal.TemporalUnit;
+import java.time.temporal.UnsupportedTemporalTypeException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import static java.time.temporal.ChronoUnit.DAYS;
+import static java.time.temporal.ChronoUnit.MONTHS;
+import static java.time.temporal.ChronoUnit.YEARS;
+
+/**
+ * This class defines new Groovy methods which appear on normal JDK
+ * Date/Time API (java.time) classes inside the Groovy environment.
+ * These extensions require JDK 8 or above.
+ */
+public class DateTimeExtensions {
+
+    // Static methods only
+    private DateTimeExtensions() {
+    }
+
+    private static final DateTimeFormatter ZONE_SHORT_FORMATTER = DateTimeFormatter.ofPattern("z");
+
+    /**
+     * For any Temporal subtype that does not use {@link java.time.temporal.ChronoUnit#SECONDS} as the unit for
+     * the upto/downto methods, should have an entry.
+     */
+    private static Map<Class<? extends Temporal>, TemporalUnit> DEFAULT_UNITS = new HashMap<>();
+
+    static {
+        DEFAULT_UNITS.put(ChronoLocalDate.class, DAYS);
+        DEFAULT_UNITS.put(YearMonth.class, MONTHS);
+        DEFAULT_UNITS.put(Year.class, YEARS);
+    }
+
+    /**
+     * A number of extension methods permit a long or int to be provided as a parameter. This method determines
+     * what the unit should be for this number.
+     */
+    private static TemporalUnit defaultUnitFor(Temporal temporal) {
+        return DEFAULT_UNITS.entrySet()
+                .stream()
+                .filter(e -> e.getKey().isAssignableFrom(temporal.getClass()))
+                .findFirst()
+                .map(Map.Entry::getValue)
+                .orElse(ChronoUnit.SECONDS);
+    }
+
+    /**
+     * Truncates a nanosecond value to milliseconds. No rounding.
+     */
+    private static int millisFromNanos(int nanos) {
+        return nanos / 1_000_000;
+    }
+
+    /* ******** java.time.temporal.Temporal extension methods ******** */
+
+    /**
+     * Iterates from this to the {@code to} {@link java.time.temporal.Temporal}, inclusive, incrementing by one
+     * unit each iteration, calling the closure once per iteration. The closure may accept a single
+     * {@link java.time.temporal.Temporal} argument.
+     * <p>
+     * The particular unit incremented by depends on the specific sub-type of {@link java.time.temporal.Temporal}.
+     * Most sub-types use a unit of {@link java.time.temporal.ChronoUnit#SECONDS} except for
+     * <ul>
+     * <li>{@link java.time.chrono.ChronoLocalDate} and its sub-types use {@link java.time.temporal.ChronoUnit#DAYS}.
+     * <li>{@link java.time.YearMonth} uses {@link java.time.temporal.ChronoUnit#MONTHS}.
+     * <li>{@link java.time.Year} uses {@link java.time.temporal.ChronoUnit#YEARS}.
+     * </ul>
+     *
+     * @param from    the starting Temporal
+     * @param to      the ending Temporal
+     * @param closure the zero or one-argument closure to call
+     * @throws GroovyRuntimeException if this value is later than {@code to}
+     * @throws GroovyRuntimeException if {@code to} is a different type than this
+     * @since 2.5.0
+     */
+    public static void upto(Temporal from, Temporal to, Closure closure) {
+        upto(from, to, defaultUnitFor(from), closure);
+    }
+
+    /**
+     * Iterates from this to the {@code to} {@link java.time.temporal.Temporal}, inclusive, incrementing by one
+     * {@code unit} each iteration, calling the closure once per iteration. The closure may accept a single
+     * {@link java.time.temporal.Temporal} argument.
+     * <p>
+     * If the unit is too large to iterate to the second Temporal exactly, such as iterating from two LocalDateTimes
+     * that are seconds apart using {@java.time.temporal.ChronoUnit#DAYS} as the unit, the iteration will cease
+     * as soon as the current value of the iteration is later than the second Temporal argument. The closure will
+     * not be called with any value later than the {@code to} value.
+     *
+     * @param from    the starting Temporal
+     * @param to      the ending Temporal
+     * @param unit    the TemporalUnit to increment by
+     * @param closure the zero or one-argument closure to call
+     * @throws GroovyRuntimeException if this value is later than {@code to}
+     * @throws GroovyRuntimeException if {@code to} is a different type than this
+     * @since 2.5.0
+     */
+    public static void upto(Temporal from, Temporal to, TemporalUnit unit, Closure closure) {
+        if (isUptoEligible(from, to)) {
+            for (Temporal i = from; isUptoEligible(i, to); i = i.plus(1, unit)) {
+                closure.call(i);
+            }
+        } else {
+            throw new GroovyRuntimeException("The argument (" + to +
+                    ") to upto() cannot be earlier than the value (" + from + ") it's called on.");
+        }
+    }
+
+    /**
+     * Returns true if the {@code from} can be iterated up to {@code to}.
+     */
+    private static boolean isUptoEligible(Temporal from, Temporal to) {
+        TemporalAmount amount = rightShift(from, to);
+        if (amount instanceof Period) {
+            return isNonnegative((Period) amount);
+        } else if (amount instanceof Duration) {
+            return isNonnegative((Duration) amount);
+        } else {
+            throw new GroovyRuntimeException("Temporal implementations of "
+                    + from.getClass().getCanonicalName() + " are not supported by upto().");
+        }
+    }
+
+    /**
+     * Iterates from this to the {@code to} {@link java.time.temporal.Temporal}, inclusive, decrementing by one
+     * unit each iteration, calling the closure once per iteration. The closure may accept a single
+     * {@link java.time.temporal.Temporal} argument.
+     * <p>
+     * The particular unit decremented by depends on the specific sub-type of {@link java.time.temporal.Temporal}.
+     * Most sub-types use a unit of {@link java.time.temporal.ChronoUnit#SECONDS} except for
+     * <ul>
+     * <li>{@link java.time.chrono.ChronoLocalDate} and its sub-types use {@link java.time.temporal.ChronoUnit#DAYS}.
+     * <li>{@link java.time.YearMonth} uses {@link java.time.temporal.ChronoUnit#MONTHS}.
+     * <li>{@link java.time.Year} uses {@link java.time.temporal.ChronoUnit#YEARS}.
+     * </ul>
+     *
+     * @param from    the starting Temporal
+     * @param to      the ending Temporal
+     * @param closure the zero or one-argument closure to call
+     * @throws GroovyRuntimeException if this value is earlier than {@code to}
+     * @throws GroovyRuntimeException if {@code to} is a different type than this
+     * @since 2.5.0
+     */
+    public static void downto(Temporal from, Temporal to, Closure closure) {
+        downto(from, to, defaultUnitFor(from), closure);
+    }
+
+    /**
+     * Iterates from this to the {@code to} {@link java.time.temporal.Temporal}, inclusive, decrementing by one
+     * {@code unit} each iteration, calling the closure once per iteration. The closure may accept a single
+     * {@link java.time.temporal.Temporal} argument.
+     * <p>
+     * If the unit is too large to iterate to the second Temporal exactly, such as iterating from two LocalDateTimes
+     * that are seconds apart using {@java.time.temporal.ChronoUnit#DAYS} as the unit, the iteration will cease
+     * as soon as the current value of the iteration is earlier than the second Temporal argument. The closure will
+     * not be called with any value earlier than the {@code to} value.
+     *
+     * @param from    the starting Temporal
+     * @param to      the ending Temporal
+     * @param unit    the TemporalUnit to increment by
+     * @param closure the zero or one-argument closure to call
+     * @throws GroovyRuntimeException if this value is earlier than {@code to}
+     * @throws GroovyRuntimeException if {@code to} is a different type than this
+     * @since 2.5.0
+     */
+    public static void downto(Temporal from, Temporal to, TemporalUnit unit, Closure closure) {
+        if (isDowntoEligible(from, to)) {
+            for (Temporal i = from; isDowntoEligible(i, to); i = i.minus(1, unit)) {
+                closure.call(i);
+            }
+        } else {
+            throw new GroovyRuntimeException("The argument (" + to +
+                    ") to downto() cannot be later than the value (" + from + ") it's called on.");
+        }
+    }
+
+    /**
+     * Returns true if the {@code from} can be iterated down to {@code to}.
+     */
+    private static boolean isDowntoEligible(Temporal from, Temporal to) {
+        TemporalAmount amount = rightShift(from, to);
+        if (amount instanceof Period) {
+            return isNonpositive((Period) amount);
+        } else if (amount instanceof Duration) {
+            return isNonpositive((Duration) amount);
+        } else {
+            throw new GroovyRuntimeException("Temporal implementations of "
+                    + from.getClass().getCanonicalName() + " are not supported by downto().");
+        }
+    }
+
+    /**
+     * Returns a {@link java.time.Duration} or {@link java.time.Period} between this (inclusive) and the {@code other}
+     * {@link java.time.temporal.Temporal} (exclusive).
+     * <p>
+     * A Period will be returned for types {@link java.time.Year}, {@link java.time.YearMonth}, and
+     * {@link java.time.chrono.ChronoLocalDate}; otherwise, a Duration will be returned.
+     * <p>
+     * Note: if the Temporal is a ChronoLocalDate but not a {@link java.time.LocalDate}, a general
+     * {@link java.time.chrono.ChronoPeriod} will be returned as per the return type of the method
+     * {@link java.time.chrono.ChronoLocalDate#until(ChronoLocalDate)} .
+     *
+     * @param self  a Temporal
+     * @param other another Temporal of the same type
+     * @return an TemporalAmount between the two Temporals
+     * @since 2.5.0
+     */
+    public static TemporalAmount rightShift(final Temporal self, Temporal other) {
+        if (!self.getClass().equals(other.getClass())) {
+            throw new GroovyRuntimeException("Temporal arguments must be of the same type.");
+        }
+        switch ((ChronoUnit) defaultUnitFor(self)) {
+            case YEARS:
+                return DateTimeStaticExtensions.between(null, (Year) self, (Year) other);
+            case MONTHS:
+                return DateTimeStaticExtensions.between(null, (YearMonth) self, (YearMonth) other);
+            case DAYS:
+                return ChronoPeriod.between((ChronoLocalDate) self, (ChronoLocalDate) other);
+            default:
+                return Duration.between(self, other);
+        }
+    }
+
+    /* ******** java.time.temporal.TemporalAccessor extension methods ******** */
+
+    /**
+     * Supports the getAt operator; equivalent to calling the
+     * {@link java.time.temporal.TemporalAccessor#getLong(java.time.temporal.TemporalField)} method.
+     *
+     * @param self  a TemporalAccessor
+     * @param field a non-null TemporalField
+     * @return the value for the field
+     * @throws DateTimeException                if a value for the field cannot be obtained
+     * @throws UnsupportedTemporalTypeException if the field is not supported
+     * @throws ArithmeticException              if numeric overflow occurs
+     * @since 2.5.0
+     */
+    public static long getAt(final TemporalAccessor self, TemporalField field) {
+        return self.getLong(field);
+    }
+
+    /* ******** java.time.temporal.TemporalAmount extension methods ******** */
+
+    /**
+     * Supports the getAt operator; equivalent to calling the
+     * {@link java.time.temporal.TemporalAmount#get(TemporalUnit)} method.
+     *
+     * @param self a TemporalAmount
+     * @param unit a non-null TemporalUnit
+     * @return the value for the field
+     * @throws DateTimeException                if a value for the field cannot be obtained
+     * @throws UnsupportedTemporalTypeException if the field is not supported
+     * @throws ArithmeticException              if numeric overflow occurs
+     * @since 2.5.0
+     */
+    public static long getAt(final TemporalAmount self, TemporalUnit unit) {
+        return self.get(unit);
+    }
+
+    /* ******** java.time.Duration extension methods ******** */
+
+    /**
+     * Returns a {@link java.time.Duration} that is {@code seconds} seconds longer than this duration.
+     *
+     * @param self    a Duration
+     * @param seconds the number of seconds to add
+     * @return a Duration
+     * @since 2.5.0
+     */
+    public static Duration plus(final Duration self, long seconds) {
+        return self.plusSeconds(seconds);
+    }
+
+    /**
+     * Returns a {@link java.time.Duration} that is {@code seconds} seconds shorter that this duration.
+     *
+     * @param self    a Duration
+     * @param seconds the number of seconds to subtract
+     * @return a Duration
+     * @since 2.5.0
+     */
+    public static Duration minus(final Duration self, long seconds) {
+        return self.minusSeconds(seconds);
+    }
+
+    /**
+     * Returns a {@link java.time.Duration} that is one second longer than this duration.
+     *
+     * @param self a Duration
+     * @return a Duration
+     * @since 2.5.0
+     */
+    public static Duration next(final Duration self) {
+        return self.plusSeconds(1);
+    }
+
+    /**
+     * Returns a {@link java.time.Duration} that is one second shorter than this duration.
+     *
+     * @param self a Duration
+     * @return a Duration
+     * @since 2.5.0
+     */
+    public static Duration previous(final Duration self) {
+        return self.minusSeconds(1);
+    }
+
+    /**
+     * Supports the unary minus operator; equivalent to calling the {@link Duration#negated()} method.
+     *
+     * @param self a Duration
+     * @return a Duration
+     * @since 2.5.0
+     */
+    public static Duration negative(final Duration self) {
+        return self.negated();
+    }
+
+    /**
+     * Supports the unary plus operator; equivalent to calling the {@link Duration#abs()} method.
+     *
+     * @param self a Duration
+     * @return a Duration
+     * @since 2.5.0
+     */
+    public static Duration positive(final Duration self) {
+        return self.abs();
+    }
+
+    /**
+     * Supports the multiplication operator; equivalent to calling the {@link Duration#multipliedBy(long)} method.
+     *
+     * @param self   a Duration
+     * @param scalar the value to multiply by
+     * @return a Duration
+     * @since 2.5.0
+     */
+    public static Duration multiply(final Duration self, long scalar) {
+        return self.multipliedBy(scalar);
+    }
+
+    /**
+     * Supports the division operator; equivalent to calling the {@link Duration#dividedBy(long)} method.
+     *
+     * @param self   a Duration
+     * @param scalar the value to divide by
+     * @return a Duration
+     * @since 2.5.0
+     */
+    public static Duration div(final Duration self, long scalar) {
+        return self.dividedBy(scalar);
+    }
+
+    /**
+     * Returns true if this duration is positive, excluding zero.
+     *
+     * @param self a Duration
+     * @return true if positive
+     * @since 2.5.0
+     */
+    public static boolean isPositive(final Duration self) {
+        return !self.isZero() && !self.isNegative();
+    }
+
+    /**
+     * Returns true if this duration is zero or positive.
+     *
+     * @param self a Duration
+     * @return true if nonnegative
+     * @since 2.5.0
+     */
+    public static boolean isNonnegative(final Duration self) {
+        return self.isZero() || !self.isNegative();
+    }
+
+    /**
+     * Returns true if this duration is zero or negative.
+     *
+     * @param self a Duration
+     * @return true if nonpositive
+     * @since 2.5.0
+     */
+    public static boolean isNonpositive(final Duration self) {
+        return self.isZero() || self.isNegative();
+    }
+
+    /* ******** java.time.Instant extension methods ******** */
+
+    /**
+     * Returns an {@link java.time.Instant} that is {@code seconds} seconds after this instant.
+     *
+     * @param self    an Instant
+     * @param seconds the number of seconds to add
+     * @return an Instant
+     * @since 2.5.0
+     */
+    public static Instant plus(final Instant self, long seconds) {
+        return self.plusSeconds(seconds);
+    }
+
+    /**
+     * Returns an {@link java.time.Instant} that is {@code seconds} seconds before this instant.
+     *
+     * @param self    an Instant
+     * @param seconds the number of seconds to subtract
+     * @return an Instant
+     * @since 2.5.0
+     */
+    public static Instant minus(final Instant self, long seconds) {
+        return self.minusSeconds(seconds);
+    }
+
+    /**
+     * Returns an {@link java.time.Instant} that is one second after this instant.
+     *
+     * @param self an Instant
+     * @return an Instant one second ahead
+     * @since 2.5.0
+     */
+    public static Instant next(final Instant self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns an {@link java.time.Instant} that one second before this instant.
+     *
+     * @param self an Instant
+     * @return an Instant one second behind
+     * @since 2.5.0
+     */
+    public static Instant previous(final Instant self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns a generally equivalent {@link java.util.Date} according the number of milliseconds since the epoch,
+     * adjusted into the system default time zone.
+     *
+     * @param self an Instant
+     * @return a Date
+     * @since 2.5.0
+     */
+    public static Date toDate(final Instant self) {
+        return new Date(self.toEpochMilli());
+    }
+
+    /**
+     * Returns a generally equivalent {@link java.util.Calendar} in the GMT time zone, truncated to milliseconds.
+     *
+     * @param self an Instant
+     * @return a Calendar
+     * @since 2.5.0
+     */
+    public static Calendar toCalendar(final Instant self) {
+        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+        cal.setTime(toDate(self));
+        return cal;
+    }
+
+    /* ******** java.time.LocalDate extension methods ******** */
+
+    /**
+     * Formats this date with the provided {@link java.time.format.DateTimeFormatter} pattern.
+     *
+     * @param self    a LocalDate
+     * @param pattern the formatting pattern
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String format(final LocalDate self, String pattern) {
+        return self.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Formats this date in the provided, localized {@link java.time.format.FormatStyle}.
+     *
+     * @param self      a LocalDate
+     * @param dateStyle the FormatStyle
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String format(final LocalDate self, FormatStyle dateStyle) {
+        return self.format(DateTimeFormatter.ofLocalizedDate(dateStyle));
+    }
+
+    /**
+     * Formats this date with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE} formatter.
+     *
+     * @param self a LocalDate
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String getDateString(final LocalDate self) {
+        return self.format(DateTimeFormatter.ISO_LOCAL_DATE);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDate} that is {@code days} days after this date.
+     *
+     * @param self a LocalDate
+     * @param days the number of days to add
+     * @return a LocalDate
+     * @since 2.5.0
+     */
+    public static LocalDate plus(final LocalDate self, long days) {
+        return self.plusDays(days);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDate} that is {@code days} days before this date.
+     *
+     * @param self a LocalDate
+     * @param days the number of days to subtract
+     * @return a LocalDate
+     * @since 2.5.0
+     */
+    public static LocalDate minus(final LocalDate self, long days) {
+        return self.minusDays(days);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDate} one day after this date.
+     *
+     * @param self a LocalDate
+     * @return the next day
+     * @since 2.5.0
+     */
+    public static LocalDate next(final LocalDate self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDate} one day before this date.
+     *
+     * @param self a LocalDate
+     * @return the previous day
+     * @since 2.5.0
+     */
+    public static LocalDate previous(final LocalDate self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.Period} equivalent to the time between this date (inclusive)
+     * and the provided {@link java.time.LocalDate} (exclusive).
+     *
+     * @param self  a LocalDate
+     * @param other another LocalDate
+     * @return a Period representing the time between the two LocalDates
+     * @since 2.5.0
+     */
+    public static Period rightShift(final LocalDate self, LocalDate other) {
+        return Period.between(self, other);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDateTime} from this date and the provided {@link java.time.LocalTime}.
+     *
+     * @param self a LocalDate
+     * @param time a LocalTime
+     * @return a LocalDateTime
+     * @since 2.5.0
+     */
+    public static LocalDateTime leftShift(final LocalDate self, LocalTime time) {
+        return LocalDateTime.of(self, time);
+    }
+
+    /**
+     * Returns a {@link java.time.OffsetDateTime} from this date and the provided {@link java.time.OffsetTime}.
+     *
+     * @param self a LocalDate
+     * @param time an OffsetTime
+     * @return an OffsetDateTime
+     * @since 2.5.0
+     */
+    public static OffsetDateTime leftShift(final LocalDate self, OffsetTime time) {
+        return time.atDate(self);
+    }
+
+    /**
+     * Returns an equivalent instance of {@link java.util.Date}.
+     * The time portion of the returned date is cleared.
+     *
+     * @param self a LocalDate
+     * @return a java.util.Date
+     * @since 2.5.0
+     */
+    public static Date toDate(final LocalDate self) {
+        return toCalendar(self).getTime();
+    }
+
+    /**
+     * Returns an equivalent instance of {@link java.util.Calendar}.
+     * The time portion of the returned calendar is cleared and the time zone is the current system default.
+     *
+     * @param self a LocalDate
+     * @return a java.util.Calendar
+     * @since 2.5.0
+     */
+    public static Calendar toCalendar(final LocalDate self) {
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.DATE, self.getDayOfMonth());
+        cal.set(Calendar.MONTH, self.getMonthValue() - 1);
+        cal.set(Calendar.YEAR, self.getYear());
+        clearTimeCommon(cal);
+        return cal;
+    }
+
+    /* duplicated with DateUtilExtensions utility method but we don't want the modules to depend on one another */
+    private static void clearTimeCommon(final Calendar self) {
+        self.set(Calendar.HOUR_OF_DAY, 0);
+        self.clear(Calendar.MINUTE);
+        self.clear(Calendar.SECOND);
+        self.clear(Calendar.MILLISECOND);
+    }
+
+    /* ******** java.time.LocalDateTime extension methods ******** */
+
+    /**
+     * Formats this date/time with the provided {@link java.time.format.DateTimeFormatter} pattern.
+     *
+     * @param self    a LocalDateTime
+     * @param pattern the formatting pattern
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String format(final LocalDateTime self, String pattern) {
+        return self.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Formats this date/time in the provided, localized {@link java.time.format.FormatStyle}.
+     *
+     * @param self          a LocalDateTime
+     * @param dateTimeStyle the FormatStyle
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String format(final LocalDateTime self, FormatStyle dateTimeStyle) {
+        return self.format(DateTimeFormatter.ofLocalizedDateTime(dateTimeStyle));
+    }
+
+    /**
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE_TIME} formatter.
+     *
+     * @param self a LocalDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String getDateTimeString(final LocalDateTime self) {
+        return self.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
+    }
+
+    /**
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE} formatter.
+     *
+     * @param self a LocalDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String getDateString(final LocalDateTime self) {
+        return self.format(DateTimeFormatter.ISO_LOCAL_DATE);
+    }
+
+    /**
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_TIME} formatter.
+     *
+     * @param self a LocalDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String getTimeString(final LocalDateTime self) {
+        return self.format(DateTimeFormatter.ISO_LOCAL_TIME);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDateTime} with the time portion cleared.
+     *
+     * @param self a LocalDateTime
+     * @return a LocalDateTime
+     * @since 2.5.0
+     */
+    public static LocalDateTime clearTime(final LocalDateTime self) {
+        return self.truncatedTo(DAYS);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDateTime} that is {@code seconds} seconds after this date/time.
+     *
+     * @param self    a LocalDateTime
+     * @param seconds the number of seconds to add
+     * @return a LocalDateTime
+     * @since 2.5.0
+     */
+    public static LocalDateTime plus(final LocalDateTime self, long seconds) {
+        return self.plusSeconds(seconds);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDateTime} that is {@code seconds} seconds before this date/time.
+     *
+     * @param self    a LocalDateTime
+     * @param seconds the number of seconds to subtract
+     * @return a LocalDateTime
+     * @since 2.5.0
+     */
+    public static LocalDateTime minus(final LocalDateTime self, long seconds) {
+        return self.minusSeconds(seconds);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDateTime} that is one second after this date/time.
+     *
+     * @param self a LocalDateTime
+     * @return a LocalDateTime
+     * @since 2.5.0
+     */
+    public static LocalDateTime next(final LocalDateTime self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDateTime} that is one second before this date/time.
+     *
+     * @param self a LocalDateTime
+     * @return a LocalDateTime
+     * @since 2.5.0
+     */
+    public static LocalDateTime previous(final LocalDateTime self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} of this date/time and the provided {@link java.time.ZoneOffset}.
+     *
+     * @param self   a LocalDateTime
+     * @param offset a ZoneOffset
+     * @return an OffsetDateTime
+     * @since 2.5.0
+     */
+    public static OffsetDateTime leftShift(final LocalDateTime self, ZoneOffset offset) {
+        return OffsetDateTime.of(self, offset);
+    }
+
+    /**
+     * Returns a {@link java.time.OffsetDateTime} of this date/time and the provided {@link java.time.ZoneId}.
+     *
+     * @param self a LocalDateTime
+     * @param zone a ZoneId
+     * @return a ZonedDateTime
+     * @since 2.5.0
+     */
+    public static ZonedDateTime leftShift(final LocalDateTime self, ZoneId zone) {
+        return ZonedDateTime.of(self, zone);
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Date}.
+     * The time value of the returned date is truncated to milliseconds.
+     *
+     * @param self a LocalDateTime
+     * @return a java.util.Date
+     * @since 2.5.0
+     */
+    public static Date toDate(final LocalDateTime self) {
+        return toCalendar(self).getTime();
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Calendar}.
+     * The time value of the returned calendar is truncated to milliseconds and the
+     * time zone is the current system default.
+     *
+     * @param self a LocalDateTime
+     * @return a java.util.Calendar
+     * @since 2.5.0
+     */
+    public static Calendar toCalendar(final LocalDateTime self) {
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.DATE, self.getDayOfMonth());
+        cal.set(Calendar.MONTH, self.getMonthValue() - 1);
+        cal.set(Calendar.YEAR, self.getYear());
+        cal.set(Calendar.HOUR_OF_DAY, self.getHour());
+        cal.set(Calendar.MINUTE, self.getMinute());
+        cal.set(Calendar.SECOND, self.getSecond());
+        cal.set(Calendar.MILLISECOND, millisFromNanos(self.getNano()));
+        return cal;
+    }
+
+    /* ******** java.time.LocalTime extension methods ******** */
+
+    /**
+     * Formats this time with the provided {@link java.time.format.DateTimeFormatter} pattern.
+     *
+     * @param self    a LocalDateTime
+     * @param pattern the formatting pattern
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String format(final LocalTime self, String pattern) {
+        return self.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Formats this time in the provided, localized {@link java.time.format.FormatStyle}.
+     *
+     * @param self      a LocalTime
+     * @param timeStyle the FormatStyle
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String format(final LocalTime self, FormatStyle timeStyle) {
+        return self.format(DateTimeFormatter.ofLocalizedTime(timeStyle));
+    }
+
+    /**
+     * Formats this time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_TIME} formatter.
+     *
+     * @param self a LocalTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String getTimeString(final LocalTime self) {
+        return self.format(DateTimeFormatter.ISO_LOCAL_TIME);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalTime} that is {@code seconds} seconds after this time.
+     *
+     * @param self    a LocalTime
+     * @param seconds the number of seconds to add
+     * @return a LocalTime
+     * @since 2.5.0
+     */
+    public static LocalTime plus(final LocalTime self, long seconds) {
+        return self.plusSeconds(seconds);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalTime} that is {@code seconds} seconds before this time.
+     *
+     * @param self    a LocalTime
+     * @param seconds the number of seconds to subtract
+     * @return a LocalTime
+     * @since 2.5.0
+     */
+    public static LocalTime minus(final LocalTime self, long seconds) {
+        return self.minusSeconds(seconds);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalTime} that is one second after this time.
+     *
+     * @param self a LocalTime
+     * @return a LocalTime
+     * @since 2.5.0
+     */
+    public static LocalTime next(final LocalTime self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalTime} that is one second before this time.
+     *
+     * @param self a LocalTime
+     * @return a LocalTime
+     * @since 2.5.0
+     */
+    public static LocalTime previous(final LocalTime self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDateTime} of this time and the provided {@link java.time.LocalDate}.
+     *
+     * @param self a LocalTime
+     * @param date a LocalDate
+     * @return a LocalDateTime
+     * @since 2.5.0
+     */
+    public static LocalDateTime leftShift(final LocalTime self, LocalDate date) {
+        return LocalDateTime.of(date, self);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetTime} of this time and the provided {@link java.time.ZoneOffset}.
+     *
+     * @param self   a LocalTime
+     * @param offset a ZoneOffset
+     * @return an OffsetTime
+     * @since 2.5.0
+     */
+    public static OffsetTime leftShift(final LocalTime self, ZoneOffset offset) {
+        return OffsetTime.of(self, offset);
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Date}. The day-month-year value of the
+     * returned date is today and the time is truncated to milliseconds.
+     *
+     * @param self a LocalTime
+     * @return a java.util.Date
+     * @since 2.5.0
+     */
+    public static Date toDate(final LocalTime self) {
+        return toCalendar(self).getTime();
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Calendar}. The day-month-year value of the
+     * returned calendar is today, the time is truncated to milliseconds, and the time zone is the current
+     * system default.
+     *
+     * @param self a LocalTime
+     * @return a java.util.Calendar
+     * @since 2.5.0
+     */
+    public static Calendar toCalendar(final LocalTime self) {
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.HOUR_OF_DAY, self.getHour());
+        cal.set(Calendar.MINUTE, self.getMinute());
+        cal.set(Calendar.SECOND, self.getSecond());
+        cal.set(Calendar.MILLISECOND, millisFromNanos(self.getNano()));
+        return cal;
+    }
+
+    /* ******** java.time.MonthDay extension methods ******** */
+
+    /**
+     * Returns a {@link java.time.LocalDate} of this month/day and the provided year.
+     *
+     * @param self a MonthDay
+     * @param year a year
+     * @return a LocalDate
+     * @since 2.5.0
+     */
+    public static LocalDate leftShift(final MonthDay self, int year) {
+        return self.atYear(year);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDate} of this month/day and the provided {@link java.time.Year}.
+     *
+     * @param self a MonthDay
+     * @param year a Year
+     * @return a LocalDate
+     * @since 2.5.0
+     */
+    public static LocalDate leftShift(final MonthDay self, Year year) {
+        return year.atMonthDay(self);
+    }
+
+    /* ******** java.time.OffsetDateTime extension methods ******** */
+
+    /**
+     * Formats this date/time with the provided {@link java.time.format.DateTimeFormatter} pattern.
+     *
+     * @param self    an OffsetDateTime
+     * @param pattern the formatting pattern
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String format(final OffsetDateTime self, String pattern) {
+        return self.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Formats this date/time in the provided, localized {@link java.time.format.FormatStyle}.
+     *
+     * @param self          an OffsetDateTime
+     * @param dateTimeStyle the FormatStyle
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String format(final OffsetDateTime self, FormatStyle dateTimeStyle) {
+        return self.format(DateTimeFormatter.ofLocalizedDateTime(dateTimeStyle));
+    }
+
+    /**
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_OFFSET_DATE_TIME} formatter.
+     *
+     * @param self an OffsetDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String getDateTimeString(final OffsetDateTime self) {
+        return self.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+    }
+
+    /**
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_OFFSET_DATE} formatter.
+     *
+     * @param self an OffsetDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String getDateString(final OffsetDateTime self) {
+        return self.format(DateTimeFormatter.ISO_OFFSET_DATE);
+    }
+
+    /**
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_OFFSET_TIME} formatter.
+     *
+     * @param self an OffsetDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String getTimeString(final OffsetDateTime self) {
+        return self.format(DateTimeFormatter.ISO_OFFSET_TIME);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} with the time portion cleared.
+     *
+     * @param self an OffsetDateTime
+     * @return an OffsetDateTime
+     * @since 2.5.0
+     */
+    public static OffsetDateTime clearTime(final OffsetDateTime self) {
+        return self.truncatedTo(DAYS);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} that is {@code seconds} seconds after this date/time.
+     *
+     * @param self    an OffsetDateTime
+     * @param seconds the number of seconds to add
+     * @return an OffsetDateTime
+     * @since 2.5.0
+     */
+    public static OffsetDateTime plus(final OffsetDateTime self, long seconds) {
+        return self.plusSeconds(seconds);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} that is {@code seconds} seconds before this date/time.
+     *
+     * @param self    an OffsetDateTime
+     * @param seconds the number of seconds to subtract
+     * @return an OffsetDateTime
+     * @since 2.5.0
+     */
+    public static OffsetDateTime minus(final OffsetDateTime self, long seconds) {
+        return self.minusSeconds(seconds);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} one second after this date/time.
+     *
+     * @param self an OffsetDateTime
+     * @return an OffsetDateTime
+     * @since 2.5.0
+     */
+    public static OffsetDateTime next(final OffsetDateTime self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} one second before this date/time.
+     *
+     * @param self an OffsetDateTime
+     * @return an OffsetDateTime
+     * @since 2.5.0
+     */
+    public static OffsetDateTime previous(final OffsetDateTime self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Date}.
+     * The time value of the returned date is truncated to milliseconds and will be
+     * adjusted to the current system default time zone.
+     *
+     * @param self an OffsetDateTime
+     * @return a java.util.Date
+     * @since 2.5.0
+     */
+    public static Date toDate(final OffsetDateTime self) {
+        return toCalendar(self).getTime();
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Calendar}.
+     * The time value of the returned calendar is truncated to milliseconds and the time zone
+     * is based on the offset of this date/time.
+     *
+     * @param self an OffsetDateTime
+     * @return a java.util.Calendar
+     * @since 2.5.0
+     */
+    public static Calendar toCalendar(final OffsetDateTime self) {
+        return toCalendar(self.toZonedDateTime());
+    }
+
+    /* ******** java.time.OffsetTime extension methods ******** */
+
+    /**
+     * Formats this time with the provided {@link java.time.format.DateTimeFormatter} pattern.
+     *
+     * @param self    an OffsetTime
+     * @param pattern the formatting pattern
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String format(final OffsetTime self, String pattern) {
+        return self.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Formats this time in the provided, localized {@link java.time.format.FormatStyle}.
+     *
+     * @param self      an OffsetTime
+     * @param timeStyle the FormatStyle
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String format(final OffsetTime self, FormatStyle timeStyle) {
+        return self.format(DateTimeFormatter.ofLocalizedTime(timeStyle));
+    }
+
+    /**
+     * Formats this time with the {@link java.time.format.DateTimeFormatter#ISO_OFFSET_TIME} formatter.
+     *
+     * @param self an OffsetTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String getTimeString(final OffsetTime self) {
+        return self.format(DateTimeFormatter.ISO_OFFSET_TIME);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetTime} that is {@code seconds} seconds after this time.
+     *
+     * @param self    an OffsetTime
+     * @param seconds the number of seconds to add
+     * @return an OffsetTime
+     * @since 2.5.0
+     */
+    public static OffsetTime plus(final OffsetTime self, long seconds) {
+        return self.plusSeconds(seconds);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetTime} that is {@code seconds} seconds before this time.
+     *
+     * @param self    an OffsetTime
+     * @param seconds the number of seconds to subtract
+     * @return an OffsetTime
+     * @since 2.5.0
+     */
+    public static OffsetTime minus(final OffsetTime self, long seconds) {
+        return self.minusSeconds(seconds);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetTime} that is one second after this time.
+     *
+     * @param self an OffsetTime
+     * @return an OffsetTime
+     * @since 2.5.0
+     */
+    public static OffsetTime next(final OffsetTime self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetTime} that is one second before this time.
+     *
+     * @param self an OffsetTime
+     * @return an OffsetTime
+     * @since 2.5.0
+     */
+    public static OffsetTime previous(final OffsetTime self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} of this time and the provided {@link java.time.LocalDate}.
+     *
+     * @param self an OffsetTime
+     * @param date a LocalDate
+     * @return an OffsetDateTime
+     * @since 2.5.0
+     */
+    public static OffsetDateTime leftShift(final OffsetTime self, LocalDate date) {
+        return OffsetDateTime.of(date, self.toLocalTime(), self.getOffset());
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Date}.
+     * The time value of the returned date is truncated to milliseconds and will be
+     * adjusted to the current system default time zone.
+     *
+     * @param self an OffsetTime
+     * @return a java.util.Date
+     * @since 2.5.0
+     */
+    public static Date toDate(final OffsetTime self) {
+        return toCalendar(self).getTime();
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Calendar}.
+     * The date value of the returned calendar is now, the time value is truncated to milliseconds,
+     * and the time zone is based on the offset of this time.
+     *
+     * @param self an OffsetTime
+     * @return a java.util.Calendar
+     * @since 2.5.0
+     */
+    public static Calendar toCalendar(final OffsetTime self) {
+        TimeZone timeZone = toTimeZone(self.getOffset());
+        Calendar cal = Calendar.getInstance(timeZone);
+        cal.set(Calendar.HOUR_OF_DAY, self.getHour());
+        cal.set(Calendar.MINUTE, self.getMinute());
+        cal.set(Calendar.SECOND, self.getSecond());
+        cal.set(Calendar.MILLISECOND, millisFromNanos(self.getNano()));
+        return cal;
+    }
+
+    /* ******** java.time.Period extension methods ******** */
+
+    /**
+     * Returns a {@link java.time.Period} that is {@code days} days longer than this period.
+     * No normalization is performed.
+     *
+     * @param self a Period
+     * @param days the number of days to increase this Period by
+     * @return a Period
+     * @since 2.5.0
+     */
+    public static Period plus(final Period self, long days) {
+        return self.plusDays(days);
+    }
+
+    /**
+     * Returns a {@link java.time.Period} that is {@code days} days shorter than this period.
+     * No normalization is performed.
+     *
+     * @param self a Period
+     * @param days the number of days to decrease this Period by
+     * @return a Period
+     * @since 2.5.0
+     */
+    public static Period minus(final Period self, long days) {
+        return self.minusDays(days);
+    }
+
+    /**
+     * Returns a {@link java.time.Period} that is one day longer than this period.
+     * No normalization is performed.
+     *
+     * @param self a Period
+     * @return a Period one day longer in length
+     * @since 2.5.0
+     */
+    public static Period next(final Period self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.Period} that is one day shorter than this period.
+     * No normalization is performed.
+     *
+     * @param self a Period
+     * @return a Period one day shorter in length
+     * @since 2.5.0
+     */
+    public static Period previous(final Period self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Supports the unary minus operator; equivalent to calling the {@link java.time.Period#negated()} method.
+     *
+     * @param self a Period
+     * @return a negated Period
+     * @since 2.5.0
+     */
+    public static Period negative(final Period self) {
+        return self.negated();
+    }
+
+    /**
+     * Supports the unary plus operator; returns a {@link java.time.Period} with all unit values positive.
+     * For example, a period of "2 years, -3 months, and -4 days" would result in a period of
+     * "2 years, 3 months, and 4 days." No normalization is performed.
+     *
+     * @param self a Period
+     * @return a positive Period
+     * @since 2.5.0
+     */
+    public static Period positive(final Period self) {
+        return !self.isNegative() ? self : self.withDays(Math.abs(self.getDays()))
+                .withMonths(Math.abs(self.getMonths()))
+                .withYears(Math.abs(self.getYears()));
+    }
+
+    /**
+     * Supports the multiply operator; equivalent to calling the {@link java.time.Period#multipliedBy(int)} method.
+     *
+     * @param self   a Period
+     * @param scalar a scalar to multiply each unit by
+     * @return a Period
+     * @since 2.5.0
+     */
+    public static Period multiply(final Period self, int scalar) {
+        return self.multipliedBy(scalar);
+    }
+
+    /**
+     * Returns true if this period is positive, excluding zero.
+     *
+     * @param self a ChronoPeriod
+     * @return true if positive
+     * @since 2.5.0
+     */
+    public static boolean isPositive(final ChronoPeriod self) {
+        return !self.isZero() && !self.isNegative();
+    }
+
+    /**
+     * Returns true if this period is zero or positive.
+     *
+     * @param self a ChronoPeriod
+     * @return true if nonnegative
+     * @since 2.5.0
+     */
+    public static boolean isNonnegative(final ChronoPeriod self) {
+        return self.isZero() || !self.isNegative();
+    }
+
+    /**
+     * Returns true if this period is zero or negative.
+     *
+     * @param self a ChronoPeriod
+     * @return true if nonpositive
+     * @since 2.5.0
+     */
+    public static boolean isNonpositive(final ChronoPeriod self) {
+        return self.isZero() || self.isNegative();
+    }
+
+    /* ******** java.time.Year extension methods ******** */
+
+    /**
+     * Returns a {@link java.time.Year} that is {@code years} years after this year.
+     *
+     * @param self  a Year
+     * @param years the number of years to add
+     * @return a Year
+     * @since 2.5.0
+     */
+    public static Year plus(final Year self, long years) {
+        return self.plusYears(years);
+    }
+
+    /**
+     * Returns a {@link java.time.Year} that is {@code years} years before this year.
+     *
+     * @param self  a Year
+     * @param years the number of years to subtract
+     * @return a Year
+     * @since 2.5.0
+     */
+    public static Year minus(final Year self, long years) {
+        return self.minusYears(years);
+    }
+
+    /**
+     * Returns a {@link java.time.Year} after this year.
+     *
+     * @param self a Year
+     * @return the next Year
+     * @since 2.5.0
+     */
+    public static Year next(final Year self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.Year} before this year.
+     *
+     * @param self a Year
+     * @return the previous Year
+     * @since 2.5.0
+     */
+    public static Year previous(final Year self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.Period} between the first day of this year (inclusive) and the first day of the
+     * provided {@link java.time.Year} (exclusive).
+     *
+     * @param self a Year
+     * @param year another Year
+     * @return a Period between the Years
+     * @since 2.5.0
+     */
+    public static Period rightShift(final Year self, Year year) {
+        return Period.between(self.atDay(1), year.atDay(1));
+    }
+
+    /**
+     * Returns a {@link java.time.YearMonth} of this year and the provided {@link java.time.Month}.
+     *
+     * @param self  a Year
+     * @param month a Month
+     * @return a YearMonth
+     * @since 2.5.0
+     */
+    public static YearMonth leftShift(final Year self, Month month) {
+        return self.atMonth(month);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDate} of this year on the given {@link java.time.MonthDay}.
+     *
+     * @param self     a Year
+     * @param monthDay a MonthDay
+     * @return a LocalDate
+     * @since 2.5.0
+     */
+    public static LocalDate leftShift(final Year self, MonthDay monthDay) {
+        return self.atMonthDay(monthDay);
+    }
+
+    /**
+     * Equivalent to calling the {@link java.time.Year#get(java.time.temporal.TemporalField)} method with a
+     * {@link java.time.temporal.ChronoField#ERA} argument.
+     * <p>
+     * Returns the era of the year, which is currently either 0 (BC) or 1 (AD).
+     *
+     * @param self a Year
+     * @return an int representing the era
+     * @since 2.5.0
+     */
+    public static int getEra(final Year self) {
+        return self.get(ChronoField.ERA);
+    }
+
+    /**
+     * Equivalent to calling the {@link java.time.Year#get(java.time.temporal.TemporalField)} method with a
+     * {@link java.time.temporal.ChronoField#YEAR_OF_ERA} argument.
+     * <p>
+     * Since Year=0 represents 1 BC, the yearOfEra value of Year=0 is 1, Year=-1 is 2, and so on.
+     *
+     * @param self a Year
+     * @return the year value of the era
+     * @since 2.5.0
+     */
+    public static int getYearOfEra(final Year self) {
+        return self.get(ChronoField.YEAR_OF_ERA);
+    }
+
+    /* ******** java.time.YearMonth extension methods ******** */
+
+    /**
+     * Returns a {@link java.time.YearMonth} that is {@code months} months after this year/month.
+     *
+     * @param self   a YearMonth
+     * @param months the number of months to add
+     * @return a Year
+     * @since 2.5.0
+     */
+    public static YearMonth plus(final YearMonth self, long months) {
+        return self.plusMonths(months);
+    }
+
+    /**
+     * Returns a {@link java.time.YearMonth} that is {@code months} months before this year/month.
+     *
+     * @param self   a YearMonth
+     * @param months the number of months to subtract
+     * @return a Year
+     * @since 2.5.0
+     */
+    public static YearMonth minus(final YearMonth self, long months) {
+        return self.minusMonths(months);
+    }
+
+    /**
+     * Returns a {@link java.time.YearMonth} that is the month after this year/month.
+     *
+     * @param self a YearMonth
+     * @return the next YearMonth
+     * @since 2.5.0
+     */
+    public static YearMonth next(final YearMonth self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.YearMonth} that is the month before this year/month.
+     *
+     * @param self a YearMonth
+     * @return the previous YearMonth
+     * @since 2.5.0
+     */
+    public static YearMonth previous(final YearMonth self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.LocalDate} of this year/month and the given day of the month.
+     *
+     * @param self       a YearMonth
+     * @param dayOfMonth a day of the month
+     * @return a LocalDate
+     * @since 2.5.0
+     */
+    public static LocalDate leftShift(final YearMonth self, int dayOfMonth) {
+        return self.atDay(dayOfMonth);
+    }
+
+    /**
+     * Returns a {@link java.time.Period} of time between the first day of this year/month (inclusive) and the
+     * given {@link java.time.YearMonth} (exclusive).
+     *
+     * @param self  a YearMonth
+     * @param other another YearMonth
+     * @return a Period
+     * @since 2.5.0
+     */
+    public static Period rightShift(YearMonth self, YearMonth other) {
+        return Period.between(self.atDay(1), other.atDay(1));
+    }
+
+    /* ******** java.time.ZonedDateTime extension methods ******** */
+
+    /**
+     * Formats this date/time with the provided {@link java.time.format.DateTimeFormatter} pattern.
+     *
+     * @param self    a ZonedDateTime
+     * @param pattern the formatting pattern
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String format(final ZonedDateTime self, String pattern) {
+        return self.format(DateTimeFormatter.ofPattern(pattern));
+    }
+
+    /**
+     * Formats this date/time in the provided, localized {@link java.time.format.FormatStyle}.
+     *
+     * @param self          a ZonedDateTime
+     * @param dateTimeStyle the FormatStyle
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String format(final ZonedDateTime self, FormatStyle dateTimeStyle) {
+        return self.format(DateTimeFormatter.ofLocalizedDateTime(dateTimeStyle));
+    }
+
+    /**
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE_TIME} formatter
+     * and appends the zone's short name, e.g. {@code 2018-03-10T14:34:55.144EST}.
+     *
+     * @param self a ZonedDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String getDateTimeString(final ZonedDateTime self) {
+        return self.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + self.format(ZONE_SHORT_FORMATTER);
+    }
+
+    /**
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE} formatter
+     * and appends the zone's short name, e.g. {@code 2018-03-10EST}.
+     *
+     * @param self a ZonedDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String getDateString(final ZonedDateTime self) {
+        return self.format(DateTimeFormatter.ISO_LOCAL_DATE) + self.format(ZONE_SHORT_FORMATTER);
+    }
+
+    /**
+     * Formats this date/time with the {@link java.time.format.DateTimeFormatter#ISO_LOCAL_TIME} formatter
+     * and appends the zone's short name, e.g. {@code 14:34:55.144EST}.
+     *
+     * @param self a ZonedDateTime
+     * @return a formatted String
+     * @see java.time.format.DateTimeFormatter
+     * @since 2.5.0
+     */
+    public static String getTimeString(final ZonedDateTime self) {
+        return self.format(DateTimeFormatter.ISO_LOCAL_TIME) + self.format(ZONE_SHORT_FORMATTER);
+    }
+
+    /**
+     * Returns an {@link java.time.ZonedDateTime} with the time portion cleared.
+     *
+     * @param self a ZonedDateTime
+     * @return a ZonedDateTime
+     * @since 2.5.0
+     */
+    public static ZonedDateTime clearTime(final ZonedDateTime self) {
+        return self.truncatedTo(DAYS);
+    }
+
+    /**
+     * Returns a {@link java.time.ZonedDateTime} that is {@code seconds} seconds after this date/time.
+     *
+     * @param self    an ZonedDateTime
+     * @param seconds the number of seconds to add
+     * @return a ZonedDateTime
+     * @since 2.5.0
+     */
+    public static ZonedDateTime plus(final ZonedDateTime self, long seconds) {
+        return self.plusSeconds(seconds);
+    }
+
+    /**
+     * Returns a {@link java.time.ZonedDateTime} that is {@code seconds} seconds before this date/time.
+     *
+     * @param self    a ZonedDateTime
+     * @param seconds the number of seconds to subtract
+     * @return a ZonedDateTime
+     * @since 2.5.0
+     */
+    public static ZonedDateTime minus(final ZonedDateTime self, long seconds) {
+        return self.minusSeconds(seconds);
+    }
+
+    /**
+     * Returns a {@link java.time.ZonedDateTime} that is one second after this date/time.
+     *
+     * @param self a ZonedDateTime
+     * @return a ZonedDateTime
+     * @since 2.5.0
+     */
+    public static ZonedDateTime next(final ZonedDateTime self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Returns a {@link java.time.ZonedDateTime} that is one second before this date/time.
+     *
+     * @param self a ZonedDateTime
+     * @return a ZonedDateTime
+     * @since 2.5.0
+     */
+    public static ZonedDateTime previous(final ZonedDateTime self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Date}.
+     * The time value of the returned date is truncated to milliseconds and will be
+     * adjusted to the current system default time zone.
+     *
+     * @param self a ZonedDateTime
+     * @return a java.util.Date
+     * @since 2.5.0
+     */
+    public static Date toDate(final ZonedDateTime self) {
+        return toCalendar(self).getTime();
+    }
+
+    /**
+     * Returns a generally equivalent instance of {@link java.util.Calendar}.
+     * The time value of the returned calendar is truncated to milliseconds and the time zone
+     * is determined by the zone of this date/time.
+     *
+     * @param self an ZonedDateTime
+     * @return a java.util.Calendar
+     * @since 2.5.0
+     */
+    public static Calendar toCalendar(final ZonedDateTime self) {
+        Calendar cal = Calendar.getInstance(toTimeZone(self.getZone()));
+        cal.set(Calendar.DATE, self.getDayOfMonth());
+        cal.set(Calendar.MONTH, self.getMonthValue() - 1);
+        cal.set(Calendar.YEAR, self.getYear());
+        cal.set(Calendar.HOUR_OF_DAY, self.getHour());
+        cal.set(Calendar.MINUTE, self.getMinute());
+        cal.set(Calendar.SECOND, self.getSecond());
+        cal.set(Calendar.MILLISECOND, millisFromNanos(self.getNano()));
+        return cal;
+    }
+
+    /* ******** java.time.ZoneId extension methods ******** */
+
+    /**
+     * Returns a {@link java.util.TimeZone} equivalent to this zone.
+     *
+     * @param self a ZoneId
+     * @return a TimeZone
+     * @since 2.5.0
+     */
+    public static TimeZone toTimeZone(final ZoneId self) {
+        return TimeZone.getTimeZone(self);
+    }
+
+    /**
+     * Returns the name of this zone formatted according to the {@link java.time.format.TextStyle#FULL} text style.
+     *
+     * @param self a ZoneId
+     * @return the full display name of the ZoneId
+     * @since 2.5.0
+     */
+    public static String getFullName(final ZoneId self) {
+        return getFullName(self, Locale.getDefault());
+    }
+
+    /**
+     * Returns the name of this zone formatted according to the {@link java.time.format.TextStyle#FULL} text style
+     * for the provided {@link java.util.Locale}.
+     *
+     * @param self   a ZoneId
+     * @param locale a Locale
+     * @return the full display name of the ZoneId
+     * @since 2.5.0
+     */
+    public static String getFullName(final ZoneId self, Locale locale) {
+        return self.getDisplayName(TextStyle.FULL, locale);
+    }
+
+    /**
+     * Returns the name of this zone formatted according to the {@link java.time.format.TextStyle#SHORT} text style.
+     *
+     * @param self a ZoneId
+     * @return the short display name of the ZoneId
+     * @since 2.5.0
+     */
+    public static String getShortName(final ZoneId self) {
+        return getShortName(self, Locale.getDefault());
+    }
+
+    /**
+     * Returns the name of this zone formatted according to the {@link java.time.format.TextStyle#SHORT} text style
+     * for the provided {@link java.util.Locale}.
+     *
+     * @param self   a ZoneId
+     * @param locale a Locale
+     * @return the short display name of the ZoneId
+     * @since 2.5.0
+     */
+    public static String getShortName(final ZoneId self, Locale locale) {
+        return self.getDisplayName(TextStyle.SHORT, locale);
+    }
+
+    /**
+     * Returns a {@link java.time.ZoneOffset} for this zone as of now.
+     *
+     * @param self a ZoneId
+     * @return a ZoneOffset
+     * @since 2.5.0
+     */
+    public static ZoneOffset getOffset(final ZoneId self) {
+        return getOffset(self, Instant.now());
+    }
+
+    /**
+     * Returns a {@link java.time.ZoneOffset} for this zone as of the provided {@link java.time.Instant}.
+     *
+     * @param self    a ZoneId
+     * @param instant an Instant
+     * @return a ZoneOffset
+     * @since 2.5.0
+     */
+    public static ZoneOffset getOffset(final ZoneId self, Instant instant) {
+        return self.getRules().getOffset(instant);
+    }
+
+    /**
+     * Returns a {@link java.time.ZonedDateTime} of this zone and the given {@link java.time.LocalDateTime}.
+     *
+     * @param self a ZoneId
+     * @return a ZonedDateTime
+     * @since 2.5.0
+     */
+    public static ZonedDateTime leftShift(final ZoneId self, LocalDateTime dateTime) {
+        return ZonedDateTime.of(dateTime, self);
+    }
+
+    /* ******** java.time.ZoneOffset extension methods ******** */
+
+    /**
+     * Returns a generally  equivalent {@link java.util.TimeZone}. The offset will be truncated to minutes.
+     *
+     * @param self a ZoneOffset
+     * @return a TimeZone
+     * @since 2.5.0
+     */
+    public static TimeZone toTimeZone(final ZoneOffset self) {
+        if (ZoneOffset.UTC.equals(self)) {
+            return TimeZone.getTimeZone("GMT");
+        } else if (getSeconds(self) == 0) {
+            return TimeZone.getTimeZone("GMT" + self.getId());
+        } else {
+            // TimeZone is only hours and minutes--no seconds
+            ZoneOffset noSeconds = ZoneOffset.ofHoursMinutes(getHours(self), getMinutes(self));
+            return TimeZone.getTimeZone("GMT" + noSeconds.getId());
+        }
+    }
+
+    /**
+     * Returns the value of the provided field for the ZoneOffset as if the ZoneOffset's
+     * hours/minutes/seconds were reckoned as a LocalTime.
+     */
+    private static int offsetFieldValue(ZoneOffset offset, TemporalField field) {
+        int offsetSeconds = offset.getTotalSeconds();
+        int value = LocalTime.ofSecondOfDay(Math.abs(offsetSeconds)).get(field);
+        return offsetSeconds < 0 ? value * -1 : value;
+    }
+
+    /**
+     * Returns the hours component of this offset. If the offset's total seconds are negative, a negative
+     * value will be returned.
+     *
+     * @param self a ZoneOffset
+     * @return the hours component value
+     * @since 2.5.0
+     */
+    public static int getHours(final ZoneOffset self) {
+        return offsetFieldValue(self, ChronoField.HOUR_OF_DAY);
+    }
+
+    /**
+     * Returns the minutes component of this offset. If the offset's total seconds are negative, a negative
+     * value will be returned.
+     *
+     * @param self a ZoneOffset
+     * @return the minutes component value
+     * @since 2.5.0
+     */
+    public static int getMinutes(final ZoneOffset self) {
+        return offsetFieldValue(self, ChronoField.MINUTE_OF_HOUR);
+    }
+
+    /**
+     * Returns the seconds component of this offset. This is not the same as the total seconds. For example:
+     * <pre>
+     *     def offset = ZoneOffset.ofHoursMinutesSeconds(0, 1, 1)
+     *     assert offset.seconds == 1
+     *     assert offset.totalSeconds == 61
+     * </pre>
+     * <p>
+     * If the offset's total seconds are negative, a negative value will be returned.
+     *
+     * @param self a ZoneOffset
+     * @return the seconds component value
+     * @since 2.5.0
+     */
+    public static int getSeconds(final ZoneOffset self) {
+        return offsetFieldValue(self, ChronoField.SECOND_OF_MINUTE);
+    }
+
+    /**
+     * Supports the getAt operator; equivalent to calling the
+     * {@link java.time.ZoneOffset#getLong(java.time.temporal.TemporalField)} method.
+     *
+     * @param self  a ZoneOffset
+     * @param field a TemporalField
+     * @return the ZoneOffset's field value
+     * @since 2.5.0
+     */
+    public static long getAt(final ZoneOffset self, TemporalField field) {
+        return self.getLong(field);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} of this offset and the provided {@link java.time.LocalDateTime}.
+     *
+     * @param self     a ZoneOffset
+     * @param dateTime a LocalDateTime
+     * @return an OffsetDateTime
+     * @since 2.5.0
+     */
+    public static OffsetDateTime leftShift(final ZoneOffset self, LocalDateTime dateTime) {
+        return OffsetDateTime.of(dateTime, self);
+    }
+
+    /**
+     * Returns an {@link java.time.OffsetDateTime} of this offset and the provided {@link java.time.LocalTime}.
+     *
+     * @param self a ZoneOffset
+     * @param time a LocalTime
+     * @return an OffsetTime
+     * @since 2.5.0
+     */
+    public static OffsetTime leftShift(final ZoneOffset self, LocalTime time) {
+        return OffsetTime.of(time, self);
+    }
+
+    /* ******** java.time.DayOfWeek extension methods ******** */
+
+    /**
+     * Returns the {@link java.time.DayOfWeek} that is {@code days} many days after this day of the week.
+     *
+     * @param self a DayOfWeek
+     * @param days the number of days to move forward
+     * @return the DayOfWeek
+     * @since 2.5.0
+     */
+    public static DayOfWeek plus(final DayOfWeek self, int days) {
+        int daysPerWeek = DayOfWeek.values().length;
+        int val = ((self.getValue() + days - 1) % daysPerWeek) + 1;
+        return DayOfWeek.of(val > 0 ? val : daysPerWeek + val);
+    }
+
+    /**
+     * Returns the {@link java.time.DayOfWeek} that is {@code days} many days before this day of the week.
+     *
+     * @param self a DayOfWeek
+     * @param days the number of days to move back
+     * @return the DayOfWeek
+     * @since 2.5.0
+     */
+    public static DayOfWeek minus(final DayOfWeek self, int days) {
+        return plus(self, days * -1);
+    }
+
+    /**
+     * Returns {@code true} if this day of the week is a weekend day (Saturday or Sunday).
+     *
+     * @param self a DayOfWeek
+     * @return true if this DayOfWeek is Saturday or Sunday
+     * @since 2.5.0
+     */
+    public static boolean isWeekend(final DayOfWeek self) {
+        return self == DayOfWeek.SATURDAY || self == DayOfWeek.SUNDAY;
+    }
+
+    /**
+     * Returns {@code true} if the DayOfWeek is a weekday.
+     *
+     * @return true if this DayOfWeek is Monday through Friday
+     * @since 2.5.0
+     */
+    public static boolean isWeekday(final DayOfWeek self) {
+        return !isWeekend(self);
+    }
+
+    /* ******** java.time.Month extension methods ******** */
+
+    /**
+     * Returns the {@link java.time.Month} that is {@code months} months after this month.
+     *
+     * @param self   a Month
+     * @param months the number of months move forward
+     * @return the Month
+     * @since 2.5.0
+     */
+    public static Month plus(final Month self, int months) {
+        int monthsPerYear = Month.values().length;
+        int val = ((self.getValue() + months - 1) % monthsPerYear) + 1;
+        return Month.of(val > 0 ? val : monthsPerYear + val);
+    }
+
+    /**
+     * Returns the {@link java.time.Month} that is {@code months} months before this month.
+     *
+     * @param self   a Month
+     * @param months the number of months to move back
+     * @return the Month
+     * @since 2.5.0
+     */
+    public static Month minus(final Month self, int months) {
+        return plus(self, months * -1);
+    }
+
+    /**
+     * Creates a {@link java.time.MonthDay} at the provided day of the month.
+     *
+     * @param self       a Month
+     * @param dayOfMonth a day of the month
+     * @return a MonthDay
+     * @since 2.5.0
+     */
+    public static MonthDay leftShift(final Month self, int dayOfMonth) {
+        return MonthDay.of(self, dayOfMonth);
+    }
+
+    /**
+     * Creates a {@link java.time.YearMonth} at the provided {@link java.time.Year}.
+     *
+     * @param self a Month
+     * @param year a Year
+     * @return a YearMonth
+     * @since 2.5.0
+     */
+    public static YearMonth leftShift(final Month self, Year year) {
+        return YearMonth.of(year.getValue(), self);
+    }
+
+    /**
+     * Returns the Time Zone offset of the Calendar as a {@link java.time.ZoneOffset}.
+     *
+     * @param self a Calendar
+     * @return a ZoneOffset
+     * @since 2.5.0
+     */
+    public static ZoneOffset getZoneOffset(final Calendar self) {
+        int offsetMillis = self.get(Calendar.ZONE_OFFSET) + self.get(Calendar.DST_OFFSET);
+        return ZoneOffset.ofTotalSeconds(offsetMillis / 1000);
+    }
+
+    /* duplicated with DateUtilExtensions.toCalendar() but we don't want modulkes to depend on one another */
+    private static Calendar toCalendar(Date self) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(self);
+        return cal;
+    }
+
+    /**
+     * Returns the Time Zone offset of the Date as a {@link java.time.ZoneOffset},
+     * which will typically be system's default offset.
+     *
+     * @param self a Date
+     * @return a ZoneOffset
+     * @since 2.5.0
+     */
+    public static ZoneOffset getZoneOffset(final Date self) {
+        return getZoneOffset(toCalendar(self));
+    }
+
+    /**
+     * Returns the Time Zone of the Calendar as a java.time.ZoneId.
+     *
+     * @param self a Calendar
+     * @return a ZoneId
+     * @since 2.5.0
+     */
+    public static ZoneId getZoneId(final Calendar self) {
+        return self.getTimeZone().toZoneId();
+    }
+
+    /**
+     * Returns the Time Zone of the Date as a {@link java.time.ZoneId}. This will
+     * typically be the system's default ZoneId.
+     *
+     * @param self a Date
+     * @return a ZoneId
+     * @since 2.5.0
+     */
+    public static ZoneId getZoneId(final Date self) {
+        return getZoneId(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.Year}.  If the Calendar has a different
+     * time zone than the system default, the Year will be adjusted into the default time zone.
+     *
+     * @param self a Calendar
+     * @return a Year
+     * @since 2.5.0
+     */
+    public static Year toYear(final Calendar self) {
+        return Year.of(self.get(Calendar.YEAR));
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.Year}.
+     *
+     * @param self a Date
+     * @return a Year
+     * @since 2.5.0
+     */
+    public static Year toYear(final Date self) {
+        return toYear(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.Month}. If the Calendar has a different
+     * time zone than the system default, the Month will be adjusted into the default time zone.
+     *
+     * @param self a Calendar
+     * @return a Month
+     * @since 2.5.0
+     */
+    public static Month toMonth(final Calendar self) {
+        return Month.of(self.get(Calendar.MONTH) + 1);
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.Month}.
+     *
+     * @param self a Date
+     * @return a Month
+     * @since 2.5.0
+     */
+    public static Month toMonth(final Date self) {
+        return toMonth(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.MonthDay}. If the Calendar has a different
+     * time zone than the system default, the MonthDay will be adjusted into the default time zone.
+     *
+     * @param self a Calendar
+     * @return a MonthDay
+     * @since 2.5.0
+     */
+    public static MonthDay toMonthDay(final Calendar self) {
+        return MonthDay.of(toMonth(self), self.get(Calendar.DAY_OF_MONTH));
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.MonthDay}.
+     *
+     * @param self a Date
+     * @return a MonthDay
+     * @since 2.5.0
+     */
+    public static MonthDay toMonthDay(final Date self) {
+        return toMonthDay(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.YearMonth}. If the Calendar has a different
+     * time zone than the system default, the YearMonth will be adjusted into the default time zone.
+     *
+     * @param self a Calendar
+     * @return a YearMonth
+     * @since 2.5.0
+     */
+    public static YearMonth toYearMonth(final Calendar self) {
+        return toYear(self).atMonth(toMonth(self));
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.YearMonth}.
+     *
+     * @param self a Date
+     * @return a YearMonth
+     * @since 2.5.0
+     */
+    public static YearMonth toYearMonth(final Date self) {
+        return toYearMonth(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.DayOfWeek}. If the Calendar has a different
+     * time zone than the system default, the DayOfWeek will be adjusted into the default time zone.
+     *
+     * @param self a Calendar
+     * @return a DayOfWeek
+     * @since 2.5.0
+     */
+    public static DayOfWeek toDayOfWeek(final Calendar self) {
+        return DayOfWeek.of(self.get(Calendar.DAY_OF_WEEK)).minus(1);
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.DayOfWeek}.
+     *
+     * @param self a Date
+     * @return a DayOfWeek
+     * @since 2.5.0
+     */
+    public static DayOfWeek toDayOfWeek(final Date self) {
+        return toDayOfWeek(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.LocalDate}. If the Calendar has a different
+     * time zone than the system default, the LocalDate will be adjusted into the default time zone.
+     *
+     * @param self a Calendar
+     * @return a LocalDate
+     * @since 2.5.0
+     */
+    static LocalDate toLocalDate(final Calendar self) {
+        return LocalDate.of(self.get(Calendar.YEAR), toMonth(self), self.get(Calendar.DAY_OF_MONTH));
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.LocalDate}.
+     *
+     * @param self a Date
+     * @return a LocalDate
+     * @since 2.5.0
+     */
+    public static LocalDate toLocalDate(final Date self) {
+        return toLocalDate(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.LocalTime}. If the Calendar has a different
+     * time zone than the system default, the LocalTime will be adjusted into the default time zone.
+     *
+     * @param self a Calendar
+     * @return a LocalTime
+     * @since 2.5.0
+     */
+    public static LocalTime toLocalTime(final Calendar self) {
+        int hour = self.get(Calendar.HOUR_OF_DAY);
+        int minute = self.get(Calendar.MINUTE);
+        int second = self.get(Calendar.SECOND);
+        int ns = self.get(Calendar.MILLISECOND) * 1_000_000;
+        return LocalTime.of(hour, minute, second, ns);
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.LocalTime}.
+     *
+     * @param self a Date
+     * @return a LocalTime
+     * @since 2.5.0
+     */
+    public static LocalTime toLocalTime(final Date self) {
+        return toLocalTime(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.LocalDateTime}. If the Calendar has a different
+     * time zone than the system default, the LocalDateTime will be adjusted into the default time zone.
+     *
+     * @param self a Calendar
+     * @return a LocalDateTime
+     * @since 2.5.0
+     */
+    public static LocalDateTime toLocalDateTime(final Calendar self) {
+        return LocalDateTime.of(toLocalDate(self), toLocalTime(self));
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.LocalDateTime}.
+     *
+     * @param self a Date
+     * @return a LocalDateTime
+     * @since 2.5.0
+     */
+    public static LocalDateTime toLocalDateTime(final Date self) {
+        return toLocalDateTime(toCalendar(self));
+    }
+
+    /**
+     * <p>Converts the Calendar to a corresponding {@link java.time.ZonedDateTime}.</p><p>Note that
+     * {@link java.util.GregorianCalendar} has a {@link java.util.GregorianCalendar#toZonedDateTime} method,
+     * which is commonly the specific type of Calendar in use.</p>
+     *
+     * @param self a Calendar
+     * @return a ZonedDateTime
+     * @since 2.5.0
+     */
+    public static ZonedDateTime toZonedDateTime(final Calendar self) {
+        if (self instanceof GregorianCalendar) { // would this branch ever be true?
+            return ((GregorianCalendar) self).toZonedDateTime();
+        } else {
+            return ZonedDateTime.of(toLocalDateTime(self), getZoneId(self));
+        }
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.ZonedDateTime}.
+     *
+     * @param self a Date
+     * @return a ZonedDateTime
+     * @since 2.5.0
+     */
+    public static ZonedDateTime toZonedDateTime(final Date self) {
+        return toZonedDateTime(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.OffsetDateTime}.
+     *
+     * @param self a Calendar
+     * @return an OffsetDateTime
+     * @since 2.5.0
+     */
+    public static OffsetDateTime toOffsetDateTime(final Calendar self) {
+        return OffsetDateTime.of(toLocalDateTime(self), getZoneOffset(self));
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.OffsetDateTime}.
+     *
+     * @param self a Date
+     * @return an OffsetDateTime
+     * @since 2.5.0
+     */
+    public static OffsetDateTime toOffsetDateTime(final Date self) {
+        return toOffsetDateTime(toCalendar(self));
+    }
+
+    /**
+     * Converts the Calendar to a corresponding {@link java.time.OffsetTime}.
+     *
+     * @param self a Calendar
+     * @return an OffsetTime
+     * @since 2.5.0
+     */
+    public static OffsetTime toOffsetTime(final Calendar self) {
+        return OffsetTime.of(toLocalTime(self), getZoneOffset(self));
+    }
+
+    /**
+     * Converts the Date to a corresponding {@link java.time.OffsetTime}.
+     *
+     * @param self a Date
+     * @return an OffsetTime
+     * @since 2.5.0
+     */
+    public static OffsetTime toOffsetTime(final Date self) {
+        return toOffsetTime(toCalendar(self));
+    }
+
+    /**
+     * Convenience method for converting a Calendar to a corresponding {@link java.time.Instant}.
+     *
+     * @param self a Calendar
+     * @return an Instant
+     * @since 2.5.0
+     */
+    public static Instant toInstant(final Calendar self) {
+        return self.getTime().toInstant();
+    }
+
+    /**
+     * Converts the TimeZone to a corresponding {@link java.time.ZoneOffset}. The offset is determined
+     * using the current date/time.
+     *
+     * @param self a TimeZone
+     * @return a ZoneOffset
+     * @since 2.5.0
+     */
+    public static ZoneOffset toZoneOffset(final TimeZone self) {
+        return toZoneOffset(self, Instant.now());
+    }
+
+    /**
+     * Converts this TimeZone to a corresponding {@link java.time.ZoneOffset}. The offset is determined
+     * using the date/time of specified Instant.
+     *
+     * @param self a TimeZone
+     * @return a ZoneOffset
+     * @since 2.5.0
+     */
+    public static ZoneOffset toZoneOffset(final TimeZone self, Instant instant) {
+        return self.toZoneId().getRules().getOffset(instant);
+    }
+}


[13/14] groovy git commit: move datetime extensions to their own module

Posted by pa...@apache.org.
move datetime extensions to their own module


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/718a820a
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/718a820a
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/718a820a

Branch: refs/heads/GROOVY_2_6_X
Commit: 718a820a29455ddadf3603bec52c91edb095b42f
Parents: 48b41ca
Author: paulk <pa...@asert.com.au>
Authored: Thu Mar 22 00:41:01 2018 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Thu Mar 22 00:44:00 2018 +1000

----------------------------------------------------------------------
 build.gradle                                    |    1 +
 gradle/binarycompatibility.gradle               |    2 +-
 gradle/docs.gradle                              |    5 +-
 gradle/pomconfigurer.gradle                     |    3 +
 settings.gradle                                 |    9 +-
 .../groovy/runtime/DateGroovyMethods.java       |  870 +------
 .../groovy/runtime/DateTimeGroovyMethods.java   | 2030 ---------------
 .../groovy/runtime/DefaultGroovyMethods.java    |    7 +-
 .../runtime/DefaultGroovyStaticMethods.java     |  204 --
 src/spec/doc/core-gdk.adoc                      |    4 +-
 src/spec/doc/working-with-datetime-types.adoc   |  341 ---
 .../gdk/WorkingWithDateTimeTypesTest.groovy     |  222 --
 src/test/groovy/DateTest.groovy                 |   79 -
 src/test/groovy/DateTimeTest.groovy             |  821 ------
 .../groovy/runtime/DateGroovyMethodsTest.java   |  116 -
 .../codehaus/groovy/runtime/DateGDKTest.groovy  |  275 --
 subprojects/groovy-datetime/build.gradle        |   29 +
 .../datetime/extensions/DateTimeExtensions.java | 2419 ++++++++++++++++++
 .../extensions/DateTimeStaticExtensions.java    |  250 ++
 .../spec/doc/working-with-datetime-types.adoc   |  338 +++
 .../gdk/WorkingWithDateTimeTypesTest.groovy     |  253 ++
 .../src/test/java/groovy/DateTimeTest.groovy    |  851 ++++++
 .../extensions/DateTimeExtensionsTest.java      |   94 +
 subprojects/groovy-dateutil/build.gradle        |   28 +
 .../dateutil/extensions/DateUtilExtensions.java |  778 ++++++
 .../spec/doc/working-with-dateutil-types.adoc   |   54 +
 .../gdk/WorkingWithDateUtilTypesTest.groovy     |   81 +
 .../src/test/java/groovy/DateTest.groovy        |  327 +++
 .../extensions/DateUtilExtensionsTest.java      |   64 +
 subprojects/groovy-json/build.gradle            |    1 +
 30 files changed, 5632 insertions(+), 4924 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/build.gradle
----------------------------------------------------------------------
diff --git a/build.gradle b/build.gradle
index 703fd1b..368e791 100644
--- a/build.gradle
+++ b/build.gradle
@@ -258,6 +258,7 @@ dependencies {
     antlr2 "org.apache.ant:ant-antlr:$antVersion"
 
     testCompile project(':groovy-ant')
+    testCompile project(':groovy-dateutil')
     testCompile project(':groovy-test')
     testCompile project(':groovy-macro')
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/gradle/binarycompatibility.gradle
----------------------------------------------------------------------
diff --git a/gradle/binarycompatibility.gradle b/gradle/binarycompatibility.gradle
index 3dfdbdd..0b6e04f 100644
--- a/gradle/binarycompatibility.gradle
+++ b/gradle/binarycompatibility.gradle
@@ -35,7 +35,7 @@ task checkBinaryCompatibility {
 check.dependsOn(checkBinaryCompatibility)
 
 // for comparing between versions with different modules, set excludeModules to differing modules, e.g.
-def excludeModules = ['performance', 'groovy-macro', 'tests-vm8', 'groovy-json-direct']
+def excludeModules = ['groovy-dateutil', 'groovy-datetime', 'performance', 'groovy-macro', 'tests-vm8', 'groovy-json-direct']
 //def excludeModules = []
 
 Set projectsToCheck = allprojects.findAll{ !(it.name in excludeModules) }

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/gradle/docs.gradle
----------------------------------------------------------------------
diff --git a/gradle/docs.gradle b/gradle/docs.gradle
index 9fb0ca7..ac4fc7d 100644
--- a/gradle/docs.gradle
+++ b/gradle/docs.gradle
@@ -141,8 +141,6 @@ task docGDK {
                     // either package name if in core or fully qualified path otherwise
                     arg(value: 'org.codehaus.groovy.runtime.DefaultGroovyMethods')
                     arg(value: 'org.codehaus.groovy.runtime.DefaultGroovyStaticMethods')
-                    arg(value: 'org.codehaus.groovy.runtime.DateGroovyMethods')
-                    arg(value: 'org.codehaus.groovy.runtime.DateTimeGroovyMethods')
                     arg(value: 'org.codehaus.groovy.runtime.EncodingGroovyMethods')
                     arg(value: 'org.codehaus.groovy.runtime.IOGroovyMethods')
                     arg(value: 'org.codehaus.groovy.runtime.ProcessGroovyMethods')
@@ -152,6 +150,9 @@ task docGDK {
                     arg(value: 'org.codehaus.groovy.vmplugin.v5.PluginDefaultGroovyMethods')
                     arg(value: 'org.codehaus.groovy.vmplugin.v8.PluginDefaultGroovyMethods')
                     // TODO don't hard-code these
+                    arg(value: 'subprojects/groovy-dateutil/src/main/java/org/apache/groovy/dateutil/extensions/DateUtilExtensions.java')
+                    arg(value: 'subprojects/groovy-datetime/src/main/java/org/apache/groovy/dateutil/extensions/DateTimeExtensions.java')
+                    arg(value: 'subprojects/groovy-datetime/src/main/java/org/apache/groovy/dateutil/extensions/DateTimeStaticExtensions.java')
                     arg(value: 'subprojects/groovy-sql/src/main/java/org/codehaus/groovy/runtime/SqlGroovyMethods.java')
                     arg(value: 'subprojects/groovy-swing/src/main/java/org/codehaus/groovy/runtime/SwingGroovyMethods.java')
                     arg(value: 'subprojects/groovy-xml/src/main/java/org/codehaus/groovy/runtime/XmlGroovyMethods.java')

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/gradle/pomconfigurer.gradle
----------------------------------------------------------------------
diff --git a/gradle/pomconfigurer.gradle b/gradle/pomconfigurer.gradle
index 1bbfcc2..1ebe9ed 100644
--- a/gradle/pomconfigurer.gradle
+++ b/gradle/pomconfigurer.gradle
@@ -609,6 +609,9 @@ project.ext.pomConfigureClosureWithoutTweaks = {
             contributor {
                 name 'Kent Inge Fagerland Simonsen'
             }
+            contributor {
+                name 'Tom Nichols'
+            }
         }
         mailingLists {
             mailingList {

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/settings.gradle
----------------------------------------------------------------------
diff --git a/settings.gradle b/settings.gradle
index 237782d..180ce83 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -19,6 +19,8 @@
 def subprojects = ['groovy-ant',
         'groovy-bsf',
         'groovy-console',
+        'groovy-datetime',
+        'groovy-dateutil',
         'groovy-docgenerator',
         'groovy-groovydoc',
         'groovy-groovysh',
@@ -35,13 +37,10 @@ def subprojects = ['groovy-ant',
         'tests-vm8',
         'groovy-testng',
         'groovy-xml',
-        'groovy-macro'
+        'groovy-macro',
+        'performance'
 ]
 
-if (JavaVersion.current().isJava8Compatible()) {
-    subprojects << 'performance'
-}
-
 if (hasProperty('stressTests')) {
     subprojects << 'stress'
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/src/main/java/org/codehaus/groovy/runtime/DateGroovyMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/DateGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DateGroovyMethods.java
index 0190b35..f452d13 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DateGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DateGroovyMethods.java
@@ -24,87 +24,42 @@ import groovy.lang.GroovyRuntimeException;
 import java.sql.Timestamp;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
-import java.time.*;
-import java.util.*;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Map;
+import java.util.TimeZone;
 
 /**
- * This class defines new groovy methods which appear on normal JDK
- * Date and Calendar classes inside the Groovy environment.
+ * @deprecated use DateUtilExtensions instead
  */
+@Deprecated
 public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
 
-    /**
-     * Support the subscript operator for a Date.
-     *
-     * @param self  a Date
-     * @param field a Calendar field, e.g. MONTH
-     * @return the value for the given field, e.g. FEBRUARY
-     * @see java.util.Calendar
-     * @since 1.5.5
-     */
+    @Deprecated
     public static int getAt(Date self, int field) {
         Calendar cal = Calendar.getInstance();
         cal.setTime(self);
         return cal.get(field);
     }
 
-    /**
-     * Convert a Date to a Calendar.
-     *
-     * @param self a Date
-     * @return a Calendar corresponding to the given Date
-     * @since 1.7.6
-     */
+    @Deprecated
     public static Calendar toCalendar(Date self) {
         Calendar cal = Calendar.getInstance();
         cal.setTime(self);
         return cal;
     }
 
-    /**
-     * Support the subscript operator for a Calendar.
-     *
-     * @param self  a Calendar
-     * @param field a Calendar field, e.g. MONTH
-     * @return the value for the given field, e.g. FEBRUARY
-     * @see java.util.Calendar
-     * @since 1.7.3
-     */
+    @Deprecated
     public static int getAt(Calendar self, int field) {
         return self.get(field);
     }
 
-    /**
-     * Support the subscript operator for mutating a Calendar.
-     * Example usage:
-     * <pre>
-     * import static java.util.Calendar.*
-     * def cal = Calendar.instance
-     * cal[DAY_OF_WEEK] = MONDAY
-     * cal[MONTH] = MARCH
-     * println cal.time // A Monday in March
-     * </pre>
-     *
-     * @param self  A Calendar
-     * @param field A Calendar field, e.g. MONTH
-     * @param value The value for the given field, e.g. FEBRUARY
-     * @see java.util.Calendar#set(int, int)
-     * @since 1.7.3
-     */
+    @Deprecated
     public static void putAt(Calendar self, int field, int value) {
         self.set(field, value);
     }
 
-    /**
-     * Support the subscript operator for mutating a Date.
-     *
-     * @param self  A Date
-     * @param field A Calendar field, e.g. MONTH
-     * @param value The value for the given field, e.g. FEBRUARY
-     * @see #putAt(java.util.Calendar, int, int)
-     * @see java.util.Calendar#set(int, int)
-     * @since 1.7.3
-     */
+    @Deprecated
     public static void putAt(Date self, int field, int value) {
         Calendar cal = Calendar.getInstance();
         cal.setTime(self);
@@ -112,125 +67,30 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
         self.setTime(cal.getTimeInMillis());
     }
 
-    /**
-     * Support mutating a Calendar with a Map.
-     * <p>
-     * The map values are the normal values provided as the
-     * second parameter to <code>java.util.Calendar#set(int, int)</code>.
-     * The keys can either be the normal fields values provided as
-     * the first parameter to that method or one of the following Strings:
-     * <table border="1" cellpadding="4">
-     *   <caption>Calendar index values</caption>
-     *   <tr><td>year</td><td>Calendar.YEAR</td></tr>
-     *   <tr><td>month</td><td>Calendar.MONTH</td></tr>
-     *   <tr><td>date</td><td>Calendar.DATE</td></tr>
-     *   <tr><td>dayOfMonth</td><td>Calendar.DATE</td></tr>
-     *   <tr><td>hourOfDay</td><td>Calendar.HOUR_OF_DAY</td></tr>
-     *   <tr><td>minute</td><td>Calendar.MINUTE</td></tr>
-     *   <tr><td>second</td><td>Calendar.SECOND</td></tr>
-     * </table>
-     * Example usage:
-     * <pre>
-     * import static java.util.Calendar.*
-     * def cal = Calendar.instance
-     * def m = [:]
-     * m[YEAR] = 2010
-     * m[MONTH] = DECEMBER
-     * m[DATE] = 25
-     * cal.set(m)
-     * println cal.time // Christmas 2010
-     *
-     * cal.set(year:2011, month:DECEMBER, date:25)
-     * println cal.time // Christmas 2010
-     * </pre>
-     *
-     * @param self    A Calendar
-     * @param updates A Map of Calendar keys and values
-     * @see java.util.Calendar#set(int, int)
-     * @see java.util.Calendar#set(int, int, int, int, int, int)
-     * @since 1.7.3
-     */
+    @Deprecated
     public static void set(Calendar self, Map<Object, Integer> updates) {
         for (Map.Entry<Object, Integer> entry : updates.entrySet()) {
             Object key = entry.getKey();
-            if (key instanceof String) key = CAL_MAP.get(key);
+//            if (key instanceof String) key = CAL_MAP.get(key);
             if (key instanceof Integer) self.set((Integer) key, entry.getValue());
         }
     }
 
-    /**
-     * Legacy alias for copyWith. Will be deprecated and removed in future versions of Groovy.
-     *
-     * @see #copyWith(java.util.Calendar, java.util.Map)
-     * @since 1.7.3
-     */
+    @Deprecated
     public static Calendar updated(Calendar self, Map<Object, Integer> updates) {
         Calendar result = (Calendar) self.clone();
         set(result, updates);
         return result;
     }
 
-    /**
-     * Support creating a new Date having similar properties to
-     * an existing Date (which remains unaltered) but with
-     * some fields updated according to a Map of changes.
-     * <p>
-     * Example usage:
-     * <pre>
-     * import static java.util.Calendar.YEAR
-     * def now = Calendar.instance
-     * def nextYear = now[YEAR] + 1
-     * def oneYearFromNow = now.copyWith(year: nextYear)
-     * println now.time
-     * println oneYearFromNow.time
-     * </pre>
-     *
-     * @param self    A Calendar
-     * @param updates A Map of Calendar keys and values
-     * @return The newly created Calendar
-     * @see java.util.Calendar#set(int, int)
-     * @see java.util.Calendar#set(int, int, int, int, int, int)
-     * @see #set(java.util.Calendar, java.util.Map)
-     * @since 2.2.0
-     */
+    @Deprecated
     public static Calendar copyWith(Calendar self, Map<Object, Integer> updates) {
         Calendar result = (Calendar) self.clone();
         set(result, updates);
         return result;
     }
 
-    /**
-     * Support mutating a Date with a Map.
-     * <p>
-     * The map values are the normal values provided as the
-     * second parameter to <code>java.util.Calendar#set(int, int)</code>.
-     * The keys can either be the normal fields values provided as
-     * the first parameter to that method or one of the following Strings:
-     * <table border="1" cellpadding="4">
-     *   <caption>Calendar index values</caption>
-     *   <tr><td>year</td><td>Calendar.YEAR</td></tr>
-     *   <tr><td>month</td><td>Calendar.MONTH</td></tr>
-     *   <tr><td>date</td><td>Calendar.DATE</td></tr>
-     *   <tr><td>dayOfMonth</td><td>Calendar.DATE</td></tr>
-     *   <tr><td>hourOfDay</td><td>Calendar.HOUR_OF_DAY</td></tr>
-     *   <tr><td>minute</td><td>Calendar.MINUTE</td></tr>
-     *   <tr><td>second</td><td>Calendar.SECOND</td></tr>
-     * </table>
-     * Example usage:
-     * <pre>
-     * import static java.util.Calendar.YEAR
-     * def date = new Date()
-     * def nextYear = date[YEAR] + 1
-     * date.set(year: nextYear)
-     * println date
-     * </pre>
-     *
-     * @param self    A Date
-     * @param updates A Map of Calendar keys and values
-     * @see java.util.Calendar#set(int, int)
-     * @see #set(java.util.Calendar, java.util.Map)
-     * @since 1.7.3
-     */
+    @Deprecated
     public static void set(Date self, Map<Object, Integer> updates) {
         Calendar cal = Calendar.getInstance();
         cal.setTime(self);
@@ -238,12 +98,7 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
         self.setTime(cal.getTimeInMillis());
     }
 
-    /**
-     * Legacy alias for copyWith. Will be deprecated and removed in future versions of Groovy.
-     *
-     * @see #copyWith(java.util.Date, java.util.Map)
-     * @since 1.7.3
-     */
+    @Deprecated
     public static Date updated(Date self, Map<Object, Integer> updates) {
         Calendar cal = Calendar.getInstance();
         cal.setTime(self);
@@ -251,29 +106,7 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
         return cal.getTime();
     }
 
-    /**
-     * Support creating a new Date having similar properties to
-     * an existing Date (which remains unaltered) but with
-     * some fields updated according to a Map of changes.
-     * <p>
-     * Example usage:
-     * <pre>
-     * import static java.util.Calendar.YEAR
-     * def today = new Date()
-     * def nextYear = today[YEAR] + 1
-     * def oneYearFromNow = today.copyWith(year: nextYear)
-     * println today
-     * println oneYearFromNow
-     * </pre>
-     *
-     * @param self    A Date
-     * @param updates A Map of Calendar keys and values
-     * @return The newly created Date
-     * @see java.util.Calendar#set(int, int)
-     * @see #set(java.util.Date, java.util.Map)
-     * @see #copyWith(java.util.Calendar, java.util.Map)
-     * @since 2.2.0
-     */
+    @Deprecated
     public static Date copyWith(Date self, Map<Object, Integer> updates) {
         Calendar cal = Calendar.getInstance();
         cal.setTime(self);
@@ -281,96 +114,41 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
         return cal.getTime();
     }
 
-    private static final Map<String, Integer> CAL_MAP = new HashMap<String, Integer>();
-
-    static {
-        CAL_MAP.put("year", Calendar.YEAR);
-        CAL_MAP.put("month", Calendar.MONTH);
-        CAL_MAP.put("date", Calendar.DATE);
-        CAL_MAP.put("dayOfMonth", Calendar.DATE);
-        CAL_MAP.put("hourOfDay", Calendar.HOUR_OF_DAY);
-        CAL_MAP.put("minute", Calendar.MINUTE);
-        CAL_MAP.put("second", Calendar.SECOND);
-    }
-
-    /**
-     * Increment a Date by one day.
-     *
-     * @param self a Date
-     * @return the next days date
-     * @since 1.0
-     */
+    @Deprecated
     public static Date next(Date self) {
         return plus(self, 1);
     }
 
-    /**
-     * Increment a Calendar by one day.
-     *
-     * @param self a Calendar
-     * @return a new Calendar set to the next day
-     * @since 1.8.7
-     */
+    @Deprecated
     public static Calendar next(Calendar self) {
         Calendar result = (Calendar) self.clone();
         result.add(Calendar.DATE, 1);
         return result;
     }
 
-    /**
-     * Decrement a Calendar by one day.
-     *
-     * @param self a Calendar
-     * @return a new Calendar set to the previous day
-     * @since 1.8.7
-     */
+    @Deprecated
     public static Calendar previous(Calendar self) {
         Calendar result = (Calendar) self.clone();
         result.add(Calendar.DATE, -1);
         return result;
     }
 
-    /**
-     * Increment a java.sql.Date by one day.
-     *
-     * @param self a java.sql.Date
-     * @return the next days date
-     * @since 1.0
-     */
+    @Deprecated
     public static java.sql.Date next(java.sql.Date self) {
         return new java.sql.Date(next((Date) self).getTime());
     }
 
-    /**
-     * Decrement a Date by one day.
-     *
-     * @param self a Date
-     * @return the previous days date
-     * @since 1.0
-     */
+    @Deprecated
     public static Date previous(Date self) {
         return minus(self, 1);
     }
 
-    /**
-     * Decrement a java.sql.Date by one day.
-     *
-     * @param self a java.sql.Date
-     * @return the previous days date
-     * @since 1.0
-     */
+    @Deprecated
     public static java.sql.Date previous(java.sql.Date self) {
         return new java.sql.Date(previous((Date) self).getTime());
     }
 
-    /**
-     * Add a number of days to this date and returns the new date.
-     *
-     * @param self a Date
-     * @param days the number of days to increase
-     * @return the new date
-     * @since 1.0
-     */
+    @Deprecated
     public static Date plus(Date self, int days) {
         Calendar calendar = Calendar.getInstance();
         calendar.setTime(self);
@@ -378,25 +156,12 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
         return calendar.getTime();
     }
 
-    /**
-     * Add a number of days to this date and returns the new date.
-     *
-     * @param self a java.sql.Date
-     * @param days the number of days to increase
-     * @return the new date
-     * @since 1.0
-     */
+    @Deprecated
     public static java.sql.Date plus(java.sql.Date self, int days) {
         return new java.sql.Date(plus((Date) self, days).getTime());
     }
 
-    /**
-     * Add number of days to this Timestamp and returns the new Timestamp object.
-     *
-     * @param self a Timestamp
-     * @param days the number of days to increase
-     * @return the new Timestamp
-     */
+    @Deprecated
     public static Timestamp plus(Timestamp self, int days) {
         Calendar calendar = Calendar.getInstance();
         calendar.setTime(self);
@@ -406,53 +171,22 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
         return ts;
     }
 
-    /**
-     * Subtract a number of days from this date and returns the new date.
-     *
-     * @param self a Date
-     * @param days the number of days to subtract
-     * @return the new date
-     * @since 1.0
-     */
+    @Deprecated
     public static Date minus(Date self, int days) {
         return plus(self, -days);
     }
 
-    /**
-     * Subtract a number of days from this date and returns the new date.
-     *
-     * @param self a java.sql.Date
-     * @param days the number of days to subtract
-     * @return the new date
-     * @since 1.0
-     */
+    @Deprecated
     public static java.sql.Date minus(java.sql.Date self, int days) {
         return new java.sql.Date(minus((Date) self, days).getTime());
     }
 
-    /**
-     * Subtract a number of days from this Timestamp and returns the new Timestamp object.
-     *
-     * @param self a Timestamp
-     * @param days the number of days to subtract
-     * @return the new Timestamp
-     */
+    @Deprecated
     public static Timestamp minus(Timestamp self, int days) {
         return plus(self, -days);
     }
 
-    /**
-     * Subtract another date from this one and return the number of days of the difference.
-     * <p>
-     * Date self = Date then + (Date self - Date then)
-     * <p>
-     * IOW, if self is before then the result is a negative value.
-     *
-     * @param self a Calendar
-     * @param then another Calendar
-     * @return number of days
-     * @since 1.6.0
-     */
+    @Deprecated
     public static int minus(Calendar self, Calendar then) {
         Calendar a = self;
         Calendar b = then;
@@ -482,18 +216,7 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
         return days;
     }
 
-    /**
-     * Subtract another Date from this one and return the number of days of the difference.
-     * <p>
-     * Date self = Date then + (Date self - Date then)
-     * <p>
-     * IOW, if self is before then the result is a negative value.
-     *
-     * @param self a Date
-     * @param then another Date
-     * @return number of days
-     * @since 1.6.0
-     */
+    @Deprecated
     public static int minus(Date self, Date then) {
         Calendar a = (Calendar) Calendar.getInstance().clone();
         a.setTime(self);
@@ -502,118 +225,34 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
         return minus(a, b);
     }
 
-    /**
-     * <p>Create a String representation of this date according to the given
-     * format pattern.
-     * <p>
-     * <p>For example, if the system timezone is GMT,
-     * <code>new Date(0).format('MM/dd/yy')</code> would return the string
-     * <code>"01/01/70"</code>. See documentation for {@link java.text.SimpleDateFormat}
-     * for format pattern use.
-     * <p>
-     * <p>Note that a new DateFormat instance is created for every
-     * invocation of this method (for thread safety).
-     *
-     * @param self   a Date
-     * @param format the format pattern to use according to {@link java.text.SimpleDateFormat}
-     * @return a string representation of this date.
-     * @see java.text.SimpleDateFormat
-     * @since 1.5.7
-     */
+    @Deprecated
     public static String format(Date self, String format) {
         return new SimpleDateFormat(format).format(self);
     }
 
-    /**
-     * <p>Create a String representation of this date according to the given
-     * format pattern and timezone.
-     * <p>
-     * <p>For example:
-     * <code>
-     * def d = new Date(0)
-     * def tz = TimeZone.getTimeZone('GMT')
-     * println d.format('dd/MMM/yyyy', tz)
-     * </code> would return the string
-     * <code>"01/Jan/1970"</code>. See documentation for {@link java.text.SimpleDateFormat}
-     * for format pattern use.
-     * <p>
-     * <p>Note that a new DateFormat instance is created for every
-     * invocation of this method (for thread safety).
-     *
-     * @param self   a Date
-     * @param format the format pattern to use according to {@link java.text.SimpleDateFormat}
-     * @param tz     the TimeZone to use
-     * @return a string representation of this date.
-     * @see java.text.SimpleDateFormat
-     * @since 1.8.3
-     */
+    @Deprecated
     public static String format(Date self, String format, TimeZone tz) {
         SimpleDateFormat sdf = new SimpleDateFormat(format);
         sdf.setTimeZone(tz);
         return sdf.format(self);
     }
 
-    /**
-     * <p>Return a string representation of the 'day' portion of this date
-     * according to the locale-specific {@link java.text.DateFormat#SHORT} default format.
-     * For an "en_UK" system locale, this would be <code>dd/MM/yy</code>.
-     * <p>
-     * <p>Note that a new DateFormat instance is created for every
-     * invocation of this method (for thread safety).
-     *
-     * @param self a Date
-     * @return a string representation of this date
-     * @see java.text.DateFormat#getDateInstance(int)
-     * @see java.text.DateFormat#SHORT
-     * @since 1.5.7
-     */
+    @Deprecated
     public static String getDateString(Date self) {
         return DateFormat.getDateInstance(DateFormat.SHORT).format(self);
     }
 
-    /**
-     * <p>Return a string representation of the time portion of this date
-     * according to the locale-specific {@link java.text.DateFormat#MEDIUM} default format.
-     * For an "en_UK" system locale, this would be <code>HH:MM:ss</code>.
-     * <p>
-     * <p>Note that a new DateFormat instance is created for every
-     * invocation of this method (for thread safety).
-     *
-     * @param self a Date
-     * @return a string representing the time portion of this date
-     * @see java.text.DateFormat#getTimeInstance(int)
-     * @see java.text.DateFormat#MEDIUM
-     * @since 1.5.7
-     */
+    @Deprecated
     public static String getTimeString(Date self) {
         return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(self);
     }
 
-    /**
-     * <p>Return a string representation of the date and time time portion of
-     * this Date instance, according to the locale-specific format used by
-     * {@link java.text.DateFormat}.  This method uses the {@link java.text.DateFormat#SHORT}
-     * preset for the day portion and {@link java.text.DateFormat#MEDIUM} for the time
-     * portion of the output string.
-     * <p>
-     * <p>Note that a new DateFormat instance is created for every
-     * invocation of this method (for thread safety).
-     *
-     * @param self a Date
-     * @return a string representation of this date and time
-     * @see java.text.DateFormat#getDateTimeInstance(int, int)
-     * @since 1.5.7
-     */
+    @Deprecated
     public static String getDateTimeString(Date self) {
         return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM).format(self);
     }
 
-    /**
-     * Common code for {@link #clearTime(java.util.Calendar)} and {@link #clearTime(java.util.Date)}
-     * and {@link #clearTime(java.sql.Date)}
-     *
-     * @param self a Calendar to adjust
-     */
+    @Deprecated
     private static void clearTimeCommon(final Calendar self) {
         self.set(Calendar.HOUR_OF_DAY, 0);
         self.clear(Calendar.MINUTE);
@@ -621,14 +260,7 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
         self.clear(Calendar.MILLISECOND);
     }
 
-    /**
-     * Clears the time portion of this Date instance; useful utility where
-     * it makes sense to compare month/day/year only portions of a Date.
-     *
-     * @param self a Date
-     * @return the Date but with the time portion cleared
-     * @since 1.6.7
-     */
+    @Deprecated
     public static Date clearTime(final Date self) {
         Calendar calendar = Calendar.getInstance();
         calendar.setTime(self);
@@ -637,14 +269,7 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
         return self;
     }
 
-    /**
-     * Clears the time portion of this java.sql.Date instance; useful utility
-     * where it makes sense to compare month/day/year only portions of a Date.
-     *
-     * @param self a java.sql.Date
-     * @return the java.sql.Date but with the time portion cleared
-     * @since 1.6.7
-     */
+    @Deprecated
     public static java.sql.Date clearTime(final java.sql.Date self) {
         Calendar calendar = Calendar.getInstance();
         calendar.setTime(self);
@@ -653,56 +278,20 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
         return self;
     }
 
-    /**
-     * Clears the time portion of this Calendar instance; useful utility
-     * where it makes sense to compare month/day/year only portions of a Calendar.
-     *
-     * @param self a Calendar
-     * @return the Calendar but with the time portion cleared
-     * @since 1.6.7
-     */
+    @Deprecated
     public static Calendar clearTime(final Calendar self) {
         clearTimeCommon(self);
         return self;
     }
 
-    /**
-     * <p>Shortcut for {@link java.text.SimpleDateFormat} to output a String representation
-     * of this calendar instance.  This method respects the Calendar's assigned
-     * {@link java.util.TimeZone}, whereas calling <code>cal.time.format('HH:mm:ss')</code>
-     * would use the system timezone.
-     * <p>Note that Calendar equivalents of <code>date.getDateString()</code>
-     * and variants do not exist because those methods are Locale-dependent.
-     * Although a Calendar may be assigned a {@link java.util.Locale}, that information is
-     * lost and therefore cannot be used to control the default date/time formats
-     * provided by these methods.  Instead, the system Locale would always be
-     * used.  The alternative is to simply call
-     * {@link java.text.DateFormat#getDateInstance(int, java.util.Locale)} and pass the same Locale
-     * that was used for the Calendar.
-     *
-     * @param self    this calendar
-     * @param pattern format pattern
-     * @return String representation of this calendar with the given format.
-     * @see java.text.DateFormat#setTimeZone(java.util.TimeZone)
-     * @see java.text.SimpleDateFormat#format(java.util.Date)
-     * @see #format(java.util.Date, String)
-     * @since 1.6.0
-     */
+    @Deprecated
     public static String format(Calendar self, String pattern) {
         SimpleDateFormat sdf = new SimpleDateFormat(pattern);
         sdf.setTimeZone(self.getTimeZone());
         return sdf.format(self.getTime());
     }
 
-    /**
-     * Iterates from this date up to the given date, inclusive,
-     * incrementing by one day each time.
-     *
-     * @param self    a Date
-     * @param to      another Date to go up to
-     * @param closure the closure to call
-     * @since 2.2
-     */
+    @Deprecated
     public static void upto(Date self, Date to, Closure closure) {
         if (self.compareTo(to) <= 0) {
             for (Date i = (Date) self.clone(); i.compareTo(to) <= 0; i = next(i)) {
@@ -713,15 +302,7 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
                     ") to upto() cannot be earlier than the value (" + self + ") it's called on.");
     }
 
-    /**
-     * Iterates from the date represented by this calendar up to the date represented
-     * by the given calendar, inclusive, incrementing by one day each time.
-     *
-     * @param self    a Calendar
-     * @param to      another Calendar to go up to
-     * @param closure the closure to call
-     * @since 2.2
-     */
+    @Deprecated
     public static void upto(Calendar self, Calendar to, Closure closure) {
         if (self.compareTo(to) <= 0) {
             for (Calendar i = (Calendar) self.clone(); i.compareTo(to) <= 0; i = next(i)) {
@@ -732,15 +313,7 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
                     ") to upto() cannot be earlier than the value (" + self + ") it's called on.");
     }
 
-    /**
-     * Iterates from this date down to the given date, inclusive,
-     * decrementing by one day each time.
-     *
-     * @param self    a Date
-     * @param to      another Date to go down to
-     * @param closure the closure to call
-     * @since 2.2
-     */
+    @Deprecated
     public static void downto(Date self, Date to, Closure closure) {
         if (self.compareTo(to) >= 0) {
             for (Date i = (Date) self.clone(); i.compareTo(to) >= 0; i = previous(i)) {
@@ -751,15 +324,7 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
                     ") to downto() cannot be later than the value (" + self + ") it's called on.");
     }
 
-    /**
-     * Iterates from the date represented by this calendar up to the date represented
-     * by the given calendar, inclusive, incrementing by one day each time.
-     *
-     * @param self    a Calendar
-     * @param to      another Calendar to go down to
-     * @param closure the closure to call
-     * @since 2.2
-     */
+    @Deprecated
     public static void downto(Calendar self, Calendar to, Closure closure) {
         if (self.compareTo(to) >= 0) {
             for (Calendar i = (Calendar) self.clone(); i.compareTo(to) >= 0; i = previous(i)) {
@@ -769,347 +334,4 @@ public class DateGroovyMethods extends DefaultGroovyMethodsSupport {
             throw new GroovyRuntimeException("The argument (" + to +
                     ") to downto() cannot be later than the value (" + self + ") it's called on.");
     }
-
-    /**
-     * Returns the Time Zone offset of the Calendar as a {@link java.time.ZoneOffset}.
-     *
-     * @param self a Calendar
-     * @return a ZoneOffset
-     * @since 3.0
-     */
-    public static ZoneOffset getZoneOffset(final Calendar self) {
-        int offsetMillis = self.get(Calendar.ZONE_OFFSET) + self.get(Calendar.DST_OFFSET);
-        return ZoneOffset.ofTotalSeconds(offsetMillis / 1000);
-    }
-
-    /**
-     * Returns the Time Zone offset of the Date as a {@link java.time.ZoneOffset},
-     * which will typically be system's default offset.
-     *
-     * @param self a Date
-     * @return a ZoneOffset
-     * @since 3.0
-     */
-    public static ZoneOffset getZoneOffset(final Date self) {
-        return getZoneOffset(toCalendar(self));
-    }
-
-    /**
-     * Returns the Time Zone of the Calendar as a java.time.ZoneId.
-     *
-     * @param self a Calendar
-     * @return a ZoneId
-     * @since 3.0
-     */
-    public static ZoneId getZoneId(final Calendar self) {
-        return self.getTimeZone().toZoneId();
-    }
-
-    /**
-     * Returns the Time Zone of the Date as a {@link java.time.ZoneId}. This will
-     * typically be the system's default ZoneId.
-     *
-     * @param self a Date
-     * @return a ZoneId
-     * @since 3.0
-     */
-    public static ZoneId getZoneId(final Date self) {
-        return getZoneId(toCalendar(self));
-    }
-
-    /**
-     * Converts the Calendar to a corresponding {@link java.time.Year}.  If the Calendar has a different
-     * time zone than the system default, the Year will be adjusted into the default time zone.
-     *
-     * @param self a Calendar
-     * @return a Year
-     * @since 3.0
-     */
-    public static Year toYear(final Calendar self) {
-        return Year.of(self.get(Calendar.YEAR));
-    }
-
-    /**
-     * Converts the Date to a corresponding {@link java.time.Year}.
-     *
-     * @param self a Date
-     * @return a Year
-     * @since 3.0
-     */
-    public static Year toYear(final Date self) {
-        return toYear(toCalendar(self));
-    }
-
-    /**
-     * Converts the Calendar to a corresponding {@link java.time.Month}. If the Calendar has a different
-     * time zone than the system default, the Month will be adjusted into the default time zone.
-     *
-     * @param self a Calendar
-     * @return a Month
-     * @since 3.0
-     */
-    public static Month toMonth(final Calendar self) {
-        return Month.of(self.get(Calendar.MONTH) + 1);
-    }
-
-    /**
-     * Converts the Date to a corresponding {@link java.time.Month}.
-     *
-     * @param self a Date
-     * @return a Month
-     * @since 3.0
-     */
-    public static Month toMonth(final Date self) {
-        return toMonth(toCalendar(self));
-    }
-
-    /**
-     * Converts the Calendar to a corresponding {@link java.time.MonthDay}. If the Calendar has a different
-     * time zone than the system default, the MonthDay will be adjusted into the default time zone.
-     *
-     * @param self a Calendar
-     * @return a MonthDay
-     * @since 3.0
-     */
-    public static MonthDay toMonthDay(final Calendar self) {
-        return MonthDay.of(toMonth(self), self.get(Calendar.DAY_OF_MONTH));
-    }
-
-    /**
-     * Converts the Date to a corresponding {@link java.time.MonthDay}.
-     *
-     * @param self a Date
-     * @return a MonthDay
-     * @since 3.0
-     */
-    public static MonthDay toMonthDay(final Date self) {
-        return toMonthDay(toCalendar(self));
-    }
-
-    /**
-     * Converts the Calendar to a corresponding {@link java.time.YearMonth}. If the Calendar has a different
-     * time zone than the system default, the YearMonth will be adjusted into the default time zone.
-     *
-     * @param self a Calendar
-     * @return a YearMonth
-     * @since 3.0
-     */
-    public static YearMonth toYearMonth(final Calendar self) {
-        return toYear(self).atMonth(toMonth(self));
-    }
-
-    /**
-     * Converts the Date to a corresponding {@link java.time.YearMonth}.
-     *
-     * @param self a Date
-     * @return a YearMonth
-     * @since 3.0
-     */
-    public static YearMonth toYearMonth(final Date self) {
-        return toYearMonth(toCalendar(self));
-    }
-
-    /**
-     * Converts the Calendar to a corresponding {@link java.time.DayOfWeek}. If the Calendar has a different
-     * time zone than the system default, the DayOfWeek will be adjusted into the default time zone.
-     *
-     *
-     * @param self a Calendar
-     * @return a DayOfWeek
-     * @since 3.0
-     */
-    public static DayOfWeek toDayOfWeek(final Calendar self) {
-        return DayOfWeek.of(self.get(Calendar.DAY_OF_WEEK)).minus(1);
-    }
-
-    /**
-     * Converts the Date to a corresponding {@link java.time.DayOfWeek}.
-     *
-     * @param self a Date
-     * @return a DayOfWeek
-     * @since 3.0
-     */
-    public static DayOfWeek toDayOfWeek(final Date self) {
-        return toDayOfWeek(toCalendar(self));
-    }
-
-    /**
-     * Converts the Calendar to a corresponding {@link java.time.LocalDate}. If the Calendar has a different
-     * time zone than the system default, the LocalDate will be adjusted into the default time zone.
-     *
-     * @param self a Calendar
-     * @return a LocalDate
-     * @since 3.0
-     */
-    static LocalDate toLocalDate(final Calendar self) {
-        return LocalDate.of(self.get(Calendar.YEAR), toMonth(self), self.get(Calendar.DAY_OF_MONTH));
-    }
-
-    /**
-     * Converts the Date to a corresponding {@link java.time.LocalDate}.
-     *
-     * @param self a Date
-     * @return a LocalDate
-     * @since 3.0
-     */
-    public static LocalDate toLocalDate(final Date self) {
-        return toLocalDate(toCalendar(self));
-    }
-
-    /**
-     * Converts the Calendar to a corresponding {@link java.time.LocalTime}. If the Calendar has a different
-     * time zone than the system default, the LocalTime will be adjusted into the default time zone.
-     *
-     * @param self a Calendar
-     * @return a LocalTime
-     * @since 3.0
-     */
-    public static LocalTime toLocalTime(final Calendar self) {
-        int hour = self.get(Calendar.HOUR_OF_DAY);
-        int minute = self.get(Calendar.MINUTE);
-        int second = self.get(Calendar.SECOND);
-        int ns = self.get(Calendar.MILLISECOND) * 1_000_000;
-        return LocalTime.of(hour, minute, second, ns);
-    }
-
-    /**
-     * Converts the Date to a corresponding {@link java.time.LocalTime}.
-     *
-     * @param self a Date
-     * @return a LocalTime
-     * @since 3.0
-     */
-    public static LocalTime toLocalTime(final Date self) {
-        return toLocalTime(toCalendar(self));
-    }
-
-    /**
-     * Converts the Calendar to a corresponding {@link java.time.LocalDateTime}. If the Calendar has a different
-     * time zone than the system default, the LocalDateTime will be adjusted into the default time zone.
-     *
-     * @param self a Calendar
-     * @return a LocalDateTime
-     * @since 3.0
-     */
-    public static LocalDateTime toLocalDateTime(final Calendar self) {
-        return LocalDateTime.of(toLocalDate(self), toLocalTime(self));
-    }
-
-    /**
-     * Converts the Date to a corresponding {@link java.time.LocalDateTime}.
-     *
-     * @param self a Date
-     * @return a LocalDateTime
-     * @since 3.0
-     */
-    public static LocalDateTime toLocalDateTime(final Date self) {
-        return toLocalDateTime(toCalendar(self));
-    }
-
-    /**
-     * <p>Converts the Calendar to a corresponding {@link java.time.ZonedDateTime}.</p><p>Note that
-     * {@link java.util.GregorianCalendar} has a {@link java.util.GregorianCalendar#toZonedDateTime} method,
-     * which is commonly the specific type of Calendar in use.</p>
-     *
-     * @param self a Calendar
-     * @return a ZonedDateTime
-     * @since 3.0
-     */
-    public static ZonedDateTime toZonedDateTime(final Calendar self) {
-        if (self instanceof GregorianCalendar) { // would this branch ever be true?
-            return ((GregorianCalendar) self).toZonedDateTime();
-        } else {
-            return ZonedDateTime.of(toLocalDateTime(self), getZoneId(self));
-        }
-    }
-
-    /**
-     * Converts the Date to a corresponding {@link java.time.ZonedDateTime}.
-     *
-     * @param self a Date
-     * @return a ZonedDateTime
-     * @since 3.0
-     */
-    public static ZonedDateTime toZonedDateTime(final Date self) {
-        return toZonedDateTime(toCalendar(self));
-    }
-
-    /**
-     * Converts the Calendar to a corresponding {@link java.time.OffsetDateTime}.
-     *
-     * @param self a Calendar
-     * @return an OffsetDateTime
-     * @since 3.0
-     */
-    public static OffsetDateTime toOffsetDateTime(final Calendar self) {
-        return OffsetDateTime.of(toLocalDateTime(self), getZoneOffset(self));
-    }
-
-    /**
-     * Converts the Date to a corresponding {@link java.time.OffsetDateTime}.
-     *
-     * @param self a Date
-     * @return an OffsetDateTime
-     * @since 3.0
-     */
-    public static OffsetDateTime toOffsetDateTime(final Date self) {
-        return toOffsetDateTime(toCalendar(self));
-    }
-
-    /**
-     * Converts the Calendar to a corresponding {@link java.time.OffsetTime}.
-     *
-     * @param self a Calendar
-     * @return an OffsetTime
-     * @since 3.0
-     */
-    public static OffsetTime toOffsetTime(final Calendar self) {
-        return OffsetTime.of(toLocalTime(self), getZoneOffset(self));
-    }
-
-    /**
-     * Converts the Date to a corresponding {@link java.time.OffsetTime}.
-     *
-     * @param self a Date
-     * @return an OffsetTime
-     * @since 3.0
-     */
-    public static OffsetTime toOffsetTime(final Date self) {
-        return toOffsetTime(toCalendar(self));
-    }
-
-    /**
-     * Convenience method for converting a Calendar to a corresponding {@link java.time.Instant}.
-     *
-     * @param self a Calendar
-     * @return an Instant
-     * @since 3.0
-     */
-    public static Instant toInstant(final Calendar self) {
-        return self.getTime().toInstant();
-    }
-
-    /**
-     * Converts the TimeZone to a corresponding {@link java.time.ZoneOffset}. The offset is determined
-     * using the current date/time.
-     *
-     * @param self a TimeZone
-     * @return a ZoneOffset
-     * @since 3.0
-     */
-    public static ZoneOffset toZoneOffset(final TimeZone self) {
-        return toZoneOffset(self, Instant.now());
-    }
-
-    /**
-     * Converts this TimeZone to a corresponding {@link java.time.ZoneOffset}. The offset is determined
-     * using the date/time of specified Instant.
-     *
-     * @param self a TimeZone
-     * @return a ZoneOffset
-     * @since 3.0
-     */
-    public static ZoneOffset toZoneOffset(final TimeZone self, Instant instant) {
-        return self.toZoneId().getRules().getOffset(instant);
-    }
 }


[08/14] groovy git commit: move datetime extensions to their own module

Posted by pa...@apache.org.
http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/subprojects/groovy-dateutil/src/main/java/org/apache/groovy/dateutil/extensions/DateUtilExtensions.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-dateutil/src/main/java/org/apache/groovy/dateutil/extensions/DateUtilExtensions.java b/subprojects/groovy-dateutil/src/main/java/org/apache/groovy/dateutil/extensions/DateUtilExtensions.java
new file mode 100644
index 0000000..19cf6dd
--- /dev/null
+++ b/subprojects/groovy-dateutil/src/main/java/org/apache/groovy/dateutil/extensions/DateUtilExtensions.java
@@ -0,0 +1,778 @@
+/*
+ *  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.
+ */
+package org.apache.groovy.dateutil.extensions;
+
+import groovy.lang.Closure;
+import groovy.lang.GroovyRuntimeException;
+import org.codehaus.groovy.runtime.DefaultGroovyMethodsSupport;
+
+import java.sql.Timestamp;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TimeZone;
+
+/**
+ * This class defines new groovy methods which appear on normal JDK
+ * Date and Calendar classes inside the Groovy environment.
+ */
+public class DateUtilExtensions extends DefaultGroovyMethodsSupport {
+
+    /**
+     * Support the subscript operator for a Date.
+     *
+     * @param self  a Date
+     * @param field a Calendar field, e.g. MONTH
+     * @return the value for the given field, e.g. FEBRUARY
+     * @see java.util.Calendar
+     * @since 1.5.5
+     */
+    public static int getAt(Date self, int field) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(self);
+        return cal.get(field);
+    }
+
+    /**
+     * Convert a Date to a Calendar.
+     *
+     * @param self a Date
+     * @return a Calendar corresponding to the given Date
+     * @since 1.7.6
+     */
+    public static Calendar toCalendar(Date self) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(self);
+        return cal;
+    }
+
+    /**
+     * Support the subscript operator for a Calendar.
+     *
+     * @param self  a Calendar
+     * @param field a Calendar field, e.g. MONTH
+     * @return the value for the given field, e.g. FEBRUARY
+     * @see java.util.Calendar
+     * @since 1.7.3
+     */
+    public static int getAt(Calendar self, int field) {
+        return self.get(field);
+    }
+
+    /**
+     * Support the subscript operator for mutating a Calendar.
+     * Example usage:
+     * <pre>
+     * import static java.util.Calendar.*
+     * def cal = Calendar.instance
+     * cal[DAY_OF_WEEK] = MONDAY
+     * cal[MONTH] = MARCH
+     * println cal.time // A Monday in March
+     * </pre>
+     *
+     * @param self  A Calendar
+     * @param field A Calendar field, e.g. MONTH
+     * @param value The value for the given field, e.g. FEBRUARY
+     * @see java.util.Calendar#set(int, int)
+     * @since 1.7.3
+     */
+    public static void putAt(Calendar self, int field, int value) {
+        self.set(field, value);
+    }
+
+    /**
+     * Support the subscript operator for mutating a Date.
+     *
+     * @param self  A Date
+     * @param field A Calendar field, e.g. MONTH
+     * @param value The value for the given field, e.g. FEBRUARY
+     * @see #putAt(java.util.Calendar, int, int)
+     * @see java.util.Calendar#set(int, int)
+     * @since 1.7.3
+     */
+    public static void putAt(Date self, int field, int value) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(self);
+        putAt(cal, field, value);
+        self.setTime(cal.getTimeInMillis());
+    }
+
+    /**
+     * Support mutating a Calendar with a Map.
+     * <p>
+     * The map values are the normal values provided as the
+     * second parameter to <code>java.util.Calendar#set(int, int)</code>.
+     * The keys can either be the normal fields values provided as
+     * the first parameter to that method or one of the following Strings:
+     * <table border="1" cellpadding="4">
+     *   <caption>Calendar index values</caption>
+     *   <tr><td>year</td><td>Calendar.YEAR</td></tr>
+     *   <tr><td>month</td><td>Calendar.MONTH</td></tr>
+     *   <tr><td>date</td><td>Calendar.DATE</td></tr>
+     *   <tr><td>dayOfMonth</td><td>Calendar.DATE</td></tr>
+     *   <tr><td>hourOfDay</td><td>Calendar.HOUR_OF_DAY</td></tr>
+     *   <tr><td>minute</td><td>Calendar.MINUTE</td></tr>
+     *   <tr><td>second</td><td>Calendar.SECOND</td></tr>
+     * </table>
+     * Example usage:
+     * <pre>
+     * import static java.util.Calendar.*
+     * def cal = Calendar.instance
+     * def m = [:]
+     * m[YEAR] = 2010
+     * m[MONTH] = DECEMBER
+     * m[DATE] = 25
+     * cal.set(m)
+     * println cal.time // Christmas 2010
+     *
+     * cal.set(year:2011, month:DECEMBER, date:25)
+     * println cal.time // Christmas 2010
+     * </pre>
+     *
+     * @param self    A Calendar
+     * @param updates A Map of Calendar keys and values
+     * @see java.util.Calendar#set(int, int)
+     * @see java.util.Calendar#set(int, int, int, int, int, int)
+     * @since 1.7.3
+     */
+    public static void set(Calendar self, Map<Object, Integer> updates) {
+        for (Map.Entry<Object, Integer> entry : updates.entrySet()) {
+            Object key = entry.getKey();
+            if (key instanceof String) key = CAL_MAP.get(key);
+            if (key instanceof Integer) self.set((Integer) key, entry.getValue());
+        }
+    }
+
+    /**
+     * Legacy alias for copyWith. Will be deprecated and removed in future versions of Groovy.
+     *
+     * @see #copyWith(java.util.Calendar, java.util.Map)
+     * @since 1.7.3
+     */
+    public static Calendar updated(Calendar self, Map<Object, Integer> updates) {
+        Calendar result = (Calendar) self.clone();
+        set(result, updates);
+        return result;
+    }
+
+    /**
+     * Support creating a new Date having similar properties to
+     * an existing Date (which remains unaltered) but with
+     * some fields updated according to a Map of changes.
+     * <p>
+     * Example usage:
+     * <pre>
+     * import static java.util.Calendar.YEAR
+     * def now = Calendar.instance
+     * def nextYear = now[YEAR] + 1
+     * def oneYearFromNow = now.copyWith(year: nextYear)
+     * println now.time
+     * println oneYearFromNow.time
+     * </pre>
+     *
+     * @param self    A Calendar
+     * @param updates A Map of Calendar keys and values
+     * @return The newly created Calendar
+     * @see java.util.Calendar#set(int, int)
+     * @see java.util.Calendar#set(int, int, int, int, int, int)
+     * @see #set(java.util.Calendar, java.util.Map)
+     * @since 2.2.0
+     */
+    public static Calendar copyWith(Calendar self, Map<Object, Integer> updates) {
+        Calendar result = (Calendar) self.clone();
+        set(result, updates);
+        return result;
+    }
+
+    /**
+     * Support mutating a Date with a Map.
+     * <p>
+     * The map values are the normal values provided as the
+     * second parameter to <code>java.util.Calendar#set(int, int)</code>.
+     * The keys can either be the normal fields values provided as
+     * the first parameter to that method or one of the following Strings:
+     * <table border="1" cellpadding="4">
+     *   <caption>Calendar index values</caption>
+     *   <tr><td>year</td><td>Calendar.YEAR</td></tr>
+     *   <tr><td>month</td><td>Calendar.MONTH</td></tr>
+     *   <tr><td>date</td><td>Calendar.DATE</td></tr>
+     *   <tr><td>dayOfMonth</td><td>Calendar.DATE</td></tr>
+     *   <tr><td>hourOfDay</td><td>Calendar.HOUR_OF_DAY</td></tr>
+     *   <tr><td>minute</td><td>Calendar.MINUTE</td></tr>
+     *   <tr><td>second</td><td>Calendar.SECOND</td></tr>
+     * </table>
+     * Example usage:
+     * <pre>
+     * import static java.util.Calendar.YEAR
+     * def date = new Date()
+     * def nextYear = date[YEAR] + 1
+     * date.set(year: nextYear)
+     * println date
+     * </pre>
+     *
+     * @param self    A Date
+     * @param updates A Map of Calendar keys and values
+     * @see java.util.Calendar#set(int, int)
+     * @see #set(java.util.Calendar, java.util.Map)
+     * @since 1.7.3
+     */
+    public static void set(Date self, Map<Object, Integer> updates) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(self);
+        set(cal, updates);
+        self.setTime(cal.getTimeInMillis());
+    }
+
+    /**
+     * Legacy alias for copyWith. Will be deprecated and removed in future versions of Groovy.
+     *
+     * @see #copyWith(java.util.Date, java.util.Map)
+     * @since 1.7.3
+     */
+    public static Date updated(Date self, Map<Object, Integer> updates) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(self);
+        set(cal, updates);
+        return cal.getTime();
+    }
+
+    /**
+     * Support creating a new Date having similar properties to
+     * an existing Date (which remains unaltered) but with
+     * some fields updated according to a Map of changes.
+     * <p>
+     * Example usage:
+     * <pre>
+     * import static java.util.Calendar.YEAR
+     * def today = new Date()
+     * def nextYear = today[YEAR] + 1
+     * def oneYearFromNow = today.copyWith(year: nextYear)
+     * println today
+     * println oneYearFromNow
+     * </pre>
+     *
+     * @param self    A Date
+     * @param updates A Map of Calendar keys and values
+     * @return The newly created Date
+     * @see java.util.Calendar#set(int, int)
+     * @see #set(java.util.Date, java.util.Map)
+     * @see #copyWith(java.util.Calendar, java.util.Map)
+     * @since 2.2.0
+     */
+    public static Date copyWith(Date self, Map<Object, Integer> updates) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(self);
+        set(cal, updates);
+        return cal.getTime();
+    }
+
+    private static final Map<String, Integer> CAL_MAP = new HashMap<String, Integer>();
+
+    static {
+        CAL_MAP.put("year", Calendar.YEAR);
+        CAL_MAP.put("month", Calendar.MONTH);
+        CAL_MAP.put("date", Calendar.DATE);
+        CAL_MAP.put("dayOfMonth", Calendar.DATE);
+        CAL_MAP.put("hourOfDay", Calendar.HOUR_OF_DAY);
+        CAL_MAP.put("minute", Calendar.MINUTE);
+        CAL_MAP.put("second", Calendar.SECOND);
+    }
+
+    /**
+     * Increment a Date by one day.
+     *
+     * @param self a Date
+     * @return the next days date
+     * @since 1.0
+     */
+    public static Date next(Date self) {
+        return plus(self, 1);
+    }
+
+    /**
+     * Increment a Calendar by one day.
+     *
+     * @param self a Calendar
+     * @return a new Calendar set to the next day
+     * @since 1.8.7
+     */
+    public static Calendar next(Calendar self) {
+        Calendar result = (Calendar) self.clone();
+        result.add(Calendar.DATE, 1);
+        return result;
+    }
+
+    /**
+     * Decrement a Calendar by one day.
+     *
+     * @param self a Calendar
+     * @return a new Calendar set to the previous day
+     * @since 1.8.7
+     */
+    public static Calendar previous(Calendar self) {
+        Calendar result = (Calendar) self.clone();
+        result.add(Calendar.DATE, -1);
+        return result;
+    }
+
+    /**
+     * Increment a java.sql.Date by one day.
+     *
+     * @param self a java.sql.Date
+     * @return the next days date
+     * @since 1.0
+     */
+    public static java.sql.Date next(java.sql.Date self) {
+        return new java.sql.Date(next((Date) self).getTime());
+    }
+
+    /**
+     * Decrement a Date by one day.
+     *
+     * @param self a Date
+     * @return the previous days date
+     * @since 1.0
+     */
+    public static Date previous(Date self) {
+        return minus(self, 1);
+    }
+
+    /**
+     * Decrement a java.sql.Date by one day.
+     *
+     * @param self a java.sql.Date
+     * @return the previous days date
+     * @since 1.0
+     */
+    public static java.sql.Date previous(java.sql.Date self) {
+        return new java.sql.Date(previous((Date) self).getTime());
+    }
+
+    /**
+     * Add a number of days to this date and returns the new date.
+     *
+     * @param self a Date
+     * @param days the number of days to increase
+     * @return the new date
+     * @since 1.0
+     */
+    public static Date plus(Date self, int days) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(self);
+        calendar.add(Calendar.DATE, days);
+        return calendar.getTime();
+    }
+
+    /**
+     * Add a number of days to this date and returns the new date.
+     *
+     * @param self a java.sql.Date
+     * @param days the number of days to increase
+     * @return the new date
+     * @since 1.0
+     */
+    public static java.sql.Date plus(java.sql.Date self, int days) {
+        return new java.sql.Date(plus((Date) self, days).getTime());
+    }
+
+    /**
+     * Add number of days to this Timestamp and returns the new Timestamp object.
+     *
+     * @param self a Timestamp
+     * @param days the number of days to increase
+     * @return the new Timestamp
+     */
+    public static Timestamp plus(Timestamp self, int days) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(self);
+        calendar.add(Calendar.DATE, days);
+        Timestamp ts = new Timestamp(calendar.getTime().getTime());
+        ts.setNanos(self.getNanos());
+        return ts;
+    }
+
+    /**
+     * Subtract a number of days from this date and returns the new date.
+     *
+     * @param self a Date
+     * @param days the number of days to subtract
+     * @return the new date
+     * @since 1.0
+     */
+    public static Date minus(Date self, int days) {
+        return plus(self, -days);
+    }
+
+    /**
+     * Subtract a number of days from this date and returns the new date.
+     *
+     * @param self a java.sql.Date
+     * @param days the number of days to subtract
+     * @return the new date
+     * @since 1.0
+     */
+    public static java.sql.Date minus(java.sql.Date self, int days) {
+        return new java.sql.Date(minus((Date) self, days).getTime());
+    }
+
+    /**
+     * Subtract a number of days from this Timestamp and returns the new Timestamp object.
+     *
+     * @param self a Timestamp
+     * @param days the number of days to subtract
+     * @return the new Timestamp
+     */
+    public static Timestamp minus(Timestamp self, int days) {
+        return plus(self, -days);
+    }
+
+    /**
+     * Subtract another date from this one and return the number of days of the difference.
+     * <p>
+     * Date self = Date then + (Date self - Date then)
+     * <p>
+     * IOW, if self is before then the result is a negative value.
+     *
+     * @param self a Calendar
+     * @param then another Calendar
+     * @return number of days
+     * @since 1.6.0
+     */
+    public static int minus(Calendar self, Calendar then) {
+        Calendar a = self;
+        Calendar b = then;
+
+        boolean swap = a.before(b);
+
+        if (swap) {
+            Calendar t = a;
+            a = b;
+            b = t;
+        }
+
+        int days = 0;
+
+        b = (Calendar) b.clone();
+
+        while (a.get(Calendar.YEAR) > b.get(Calendar.YEAR)) {
+            days += 1 + (b.getActualMaximum(Calendar.DAY_OF_YEAR) - b.get(Calendar.DAY_OF_YEAR));
+            b.set(Calendar.DAY_OF_YEAR, 1);
+            b.add(Calendar.YEAR, 1);
+        }
+
+        days += a.get(Calendar.DAY_OF_YEAR) - b.get(Calendar.DAY_OF_YEAR);
+
+        if (swap) days = -days;
+
+        return days;
+    }
+
+    /**
+     * Subtract another Date from this one and return the number of days of the difference.
+     * <p>
+     * Date self = Date then + (Date self - Date then)
+     * <p>
+     * IOW, if self is before then the result is a negative value.
+     *
+     * @param self a Date
+     * @param then another Date
+     * @return number of days
+     * @since 1.6.0
+     */
+    public static int minus(Date self, Date then) {
+        Calendar a = (Calendar) Calendar.getInstance().clone();
+        a.setTime(self);
+        Calendar b = (Calendar) Calendar.getInstance().clone();
+        b.setTime(then);
+        return minus(a, b);
+    }
+
+    /**
+     * <p>Create a String representation of this date according to the given
+     * format pattern.
+     * <p>
+     * <p>For example, if the system timezone is GMT,
+     * <code>new Date(0).format('MM/dd/yy')</code> would return the string
+     * <code>"01/01/70"</code>. See documentation for {@link java.text.SimpleDateFormat}
+     * for format pattern use.
+     * <p>
+     * <p>Note that a new DateFormat instance is created for every
+     * invocation of this method (for thread safety).
+     *
+     * @param self   a Date
+     * @param format the format pattern to use according to {@link java.text.SimpleDateFormat}
+     * @return a string representation of this date.
+     * @see java.text.SimpleDateFormat
+     * @since 1.5.7
+     */
+    public static String format(Date self, String format) {
+        return new SimpleDateFormat(format).format(self);
+    }
+
+    /**
+     * <p>Create a String representation of this date according to the given
+     * format pattern and timezone.
+     * <p>
+     * <p>For example:
+     * <code>
+     * def d = new Date(0)
+     * def tz = TimeZone.getTimeZone('GMT')
+     * println d.format('dd/MMM/yyyy', tz)
+     * </code> would return the string
+     * <code>"01/Jan/1970"</code>. See documentation for {@link java.text.SimpleDateFormat}
+     * for format pattern use.
+     * <p>
+     * <p>Note that a new DateFormat instance is created for every
+     * invocation of this method (for thread safety).
+     *
+     * @param self   a Date
+     * @param format the format pattern to use according to {@link java.text.SimpleDateFormat}
+     * @param tz     the TimeZone to use
+     * @return a string representation of this date.
+     * @see java.text.SimpleDateFormat
+     * @since 1.8.3
+     */
+    public static String format(Date self, String format, TimeZone tz) {
+        SimpleDateFormat sdf = new SimpleDateFormat(format);
+        sdf.setTimeZone(tz);
+        return sdf.format(self);
+    }
+
+    /**
+     * <p>Return a string representation of the 'day' portion of this date
+     * according to the locale-specific {@link java.text.DateFormat#SHORT} default format.
+     * For an "en_UK" system locale, this would be <code>dd/MM/yy</code>.
+     * <p>
+     * <p>Note that a new DateFormat instance is created for every
+     * invocation of this method (for thread safety).
+     *
+     * @param self a Date
+     * @return a string representation of this date
+     * @see java.text.DateFormat#getDateInstance(int)
+     * @see java.text.DateFormat#SHORT
+     * @since 1.5.7
+     */
+    public static String getDateString(Date self) {
+        return DateFormat.getDateInstance(DateFormat.SHORT).format(self);
+    }
+
+    /**
+     * <p>Return a string representation of the time portion of this date
+     * according to the locale-specific {@link java.text.DateFormat#MEDIUM} default format.
+     * For an "en_UK" system locale, this would be <code>HH:MM:ss</code>.
+     * <p>
+     * <p>Note that a new DateFormat instance is created for every
+     * invocation of this method (for thread safety).
+     *
+     * @param self a Date
+     * @return a string representing the time portion of this date
+     * @see java.text.DateFormat#getTimeInstance(int)
+     * @see java.text.DateFormat#MEDIUM
+     * @since 1.5.7
+     */
+    public static String getTimeString(Date self) {
+        return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(self);
+    }
+
+    /**
+     * <p>Return a string representation of the date and time time portion of
+     * this Date instance, according to the locale-specific format used by
+     * {@link java.text.DateFormat}.  This method uses the {@link java.text.DateFormat#SHORT}
+     * preset for the day portion and {@link java.text.DateFormat#MEDIUM} for the time
+     * portion of the output string.
+     * <p>
+     * <p>Note that a new DateFormat instance is created for every
+     * invocation of this method (for thread safety).
+     *
+     * @param self a Date
+     * @return a string representation of this date and time
+     * @see java.text.DateFormat#getDateTimeInstance(int, int)
+     * @since 1.5.7
+     */
+    public static String getDateTimeString(Date self) {
+        return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM).format(self);
+    }
+
+    /**
+     * Common code for {@link #clearTime(java.util.Calendar)} and {@link #clearTime(java.util.Date)}
+     * and {@link #clearTime(java.sql.Date)}
+     *
+     * @param self a Calendar to adjust
+     */
+    private static void clearTimeCommon(final Calendar self) {
+        self.set(Calendar.HOUR_OF_DAY, 0);
+        self.clear(Calendar.MINUTE);
+        self.clear(Calendar.SECOND);
+        self.clear(Calendar.MILLISECOND);
+    }
+
+    /**
+     * Clears the time portion of this Date instance; useful utility where
+     * it makes sense to compare month/day/year only portions of a Date.
+     *
+     * @param self a Date
+     * @return the Date but with the time portion cleared
+     * @since 1.6.7
+     */
+    public static Date clearTime(final Date self) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(self);
+        clearTimeCommon(calendar);
+        self.setTime(calendar.getTime().getTime());
+        return self;
+    }
+
+    /**
+     * Clears the time portion of this java.sql.Date instance; useful utility
+     * where it makes sense to compare month/day/year only portions of a Date.
+     *
+     * @param self a java.sql.Date
+     * @return the java.sql.Date but with the time portion cleared
+     * @since 1.6.7
+     */
+    public static java.sql.Date clearTime(final java.sql.Date self) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(self);
+        clearTimeCommon(calendar);
+        self.setTime(calendar.getTime().getTime());
+        return self;
+    }
+
+    /**
+     * Clears the time portion of this Calendar instance; useful utility
+     * where it makes sense to compare month/day/year only portions of a Calendar.
+     *
+     * @param self a Calendar
+     * @return the Calendar but with the time portion cleared
+     * @since 1.6.7
+     */
+    public static Calendar clearTime(final Calendar self) {
+        clearTimeCommon(self);
+        return self;
+    }
+
+    /**
+     * <p>Shortcut for {@link java.text.SimpleDateFormat} to output a String representation
+     * of this calendar instance.  This method respects the Calendar's assigned
+     * {@link java.util.TimeZone}, whereas calling <code>cal.time.format('HH:mm:ss')</code>
+     * would use the system timezone.
+     * <p>Note that Calendar equivalents of <code>date.getDateString()</code>
+     * and variants do not exist because those methods are Locale-dependent.
+     * Although a Calendar may be assigned a {@link java.util.Locale}, that information is
+     * lost and therefore cannot be used to control the default date/time formats
+     * provided by these methods.  Instead, the system Locale would always be
+     * used.  The alternative is to simply call
+     * {@link java.text.DateFormat#getDateInstance(int, java.util.Locale)} and pass the same Locale
+     * that was used for the Calendar.
+     *
+     * @param self    this calendar
+     * @param pattern format pattern
+     * @return String representation of this calendar with the given format.
+     * @see java.text.DateFormat#setTimeZone(java.util.TimeZone)
+     * @see java.text.SimpleDateFormat#format(java.util.Date)
+     * @see #format(java.util.Date, String)
+     * @since 1.6.0
+     */
+    public static String format(Calendar self, String pattern) {
+        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+        sdf.setTimeZone(self.getTimeZone());
+        return sdf.format(self.getTime());
+    }
+
+    /**
+     * Iterates from this date up to the given date, inclusive,
+     * incrementing by one day each time.
+     *
+     * @param self    a Date
+     * @param to      another Date to go up to
+     * @param closure the closure to call
+     * @since 2.2
+     */
+    public static void upto(Date self, Date to, Closure closure) {
+        if (self.compareTo(to) <= 0) {
+            for (Date i = (Date) self.clone(); i.compareTo(to) <= 0; i = next(i)) {
+                closure.call(i);
+            }
+        } else
+            throw new GroovyRuntimeException("The argument (" + to +
+                    ") to upto() cannot be earlier than the value (" + self + ") it's called on.");
+    }
+
+    /**
+     * Iterates from the date represented by this calendar up to the date represented
+     * by the given calendar, inclusive, incrementing by one day each time.
+     *
+     * @param self    a Calendar
+     * @param to      another Calendar to go up to
+     * @param closure the closure to call
+     * @since 2.2
+     */
+    public static void upto(Calendar self, Calendar to, Closure closure) {
+        if (self.compareTo(to) <= 0) {
+            for (Calendar i = (Calendar) self.clone(); i.compareTo(to) <= 0; i = next(i)) {
+                closure.call(i);
+            }
+        } else
+            throw new GroovyRuntimeException("The argument (" + to +
+                    ") to upto() cannot be earlier than the value (" + self + ") it's called on.");
+    }
+
+    /**
+     * Iterates from this date down to the given date, inclusive,
+     * decrementing by one day each time.
+     *
+     * @param self    a Date
+     * @param to      another Date to go down to
+     * @param closure the closure to call
+     * @since 2.2
+     */
+    public static void downto(Date self, Date to, Closure closure) {
+        if (self.compareTo(to) >= 0) {
+            for (Date i = (Date) self.clone(); i.compareTo(to) >= 0; i = previous(i)) {
+                closure.call(i);
+            }
+        } else
+            throw new GroovyRuntimeException("The argument (" + to +
+                    ") to downto() cannot be later than the value (" + self + ") it's called on.");
+    }
+
+    /**
+     * Iterates from the date represented by this calendar up to the date represented
+     * by the given calendar, inclusive, incrementing by one day each time.
+     *
+     * @param self    a Calendar
+     * @param to      another Calendar to go down to
+     * @param closure the closure to call
+     * @since 2.2
+     */
+    public static void downto(Calendar self, Calendar to, Closure closure) {
+        if (self.compareTo(to) >= 0) {
+            for (Calendar i = (Calendar) self.clone(); i.compareTo(to) >= 0; i = previous(i)) {
+                closure.call(i);
+            }
+        } else
+            throw new GroovyRuntimeException("The argument (" + to +
+                    ") to downto() cannot be later than the value (" + self + ") it's called on.");
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/subprojects/groovy-dateutil/src/spec/doc/working-with-dateutil-types.adoc
----------------------------------------------------------------------
diff --git a/subprojects/groovy-dateutil/src/spec/doc/working-with-dateutil-types.adoc b/subprojects/groovy-dateutil/src/spec/doc/working-with-dateutil-types.adoc
new file mode 100644
index 0000000..9f26972
--- /dev/null
+++ b/subprojects/groovy-dateutil/src/spec/doc/working-with-dateutil-types.adoc
@@ -0,0 +1,54 @@
+//////////////////////////////////////////
+
+  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 legacy Date/Calendar types
+
+The `groovy-dateutil` module supports numerous extensions for working with
+Java's classic `Date` and `Calendar` classes.
+
+You can access the properties of a `Date` or `Calendar` using the normal array index notation
+with the constant field numbers from the `Calendar` class as shown in the following example:
+
+[source,groovy]
+-------------------------------------
+include::{projectdir}/src/spec/test/gdk/WorkingWithDateUtilTypesTest.groovy[tags=calendar_getAt,indent=0]
+-------------------------------------
+<1> Import the constants
+<2> Setting the calendar's year, month and day of month
+<3> Accessing the calendar's day of week
+
+Groovy supports arithmetic on and iteration between `Date` and `Calendar` instances as shown in the following example:
+[source,groovy]
+-------------------------------------
+include::{projectdir}/src/spec/test/gdk/WorkingWithDateUtilTypesTest.groovy[tags=date_arithmetic,indent=0]
+-------------------------------------
+
+You can parse strings into dates and output dates into formatted strings:
+[source,groovy]
+-------------------------------------
+include::{projectdir}/src/spec/test/gdk/WorkingWithDateUtilTypesTest.groovy[tags=date_parsing,indent=0]
+-------------------------------------
+
+You can also create a new Date or Calendar based on an existing one:
+[source,groovy]
+-------------------------------------
+include::{projectdir}/src/spec/test/gdk/WorkingWithDateUtilTypesTest.groovy[tags=date_copyWith,indent=0]
+-------------------------------------

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/subprojects/groovy-dateutil/src/spec/test/gdk/WorkingWithDateUtilTypesTest.groovy
----------------------------------------------------------------------
diff --git a/subprojects/groovy-dateutil/src/spec/test/gdk/WorkingWithDateUtilTypesTest.groovy b/subprojects/groovy-dateutil/src/spec/test/gdk/WorkingWithDateUtilTypesTest.groovy
new file mode 100644
index 0000000..7599867
--- /dev/null
+++ b/subprojects/groovy-dateutil/src/spec/test/gdk/WorkingWithDateUtilTypesTest.groovy
@@ -0,0 +1,81 @@
+/*
+ *  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.
+ */
+package gdk
+
+class WorkingWithDateUtilTypesTest extends GroovyTestCase {
+
+    void testGetAt() {
+        assertScript '''
+        // tag::calendar_getAt[]
+        import static java.util.Calendar.*    // <1>
+
+        def cal = Calendar.instance
+        cal[YEAR] = 2000                      // <2>
+        cal[MONTH] = JANUARY                  // <2>
+        cal[DAY_OF_MONTH] = 1                 // <2>
+        assert cal[DAY_OF_WEEK] == SATURDAY   // <3>
+        // end::calendar_getAt[]
+        '''
+    }
+
+    void testDateArithmetic() {
+        // tag::date_arithmetic[]
+        def yesterday = new Date() - 1
+        def tomorrow = new Date() + 1
+
+        def diffInDays = tomorrow - yesterday
+        assert diffInDays == 2
+
+        int count = 0
+        yesterday.upto(tomorrow) { count++ }
+        assert count == 3
+        // end::date_arithmetic[]
+    }
+
+    void testDateParsing() {
+        assertScript '''
+        import static java.util.Calendar.*
+
+        // tag::date_parsing[]
+        def orig = '2000-01-01'
+        def newYear = Date.parse('yyyy-MM-dd', orig)
+        assert newYear[DAY_OF_WEEK] == SATURDAY
+        assert newYear.format('yyyy-MM-dd') == orig
+        assert newYear.format('dd/MM/yyyy') == '01/01/2000'
+        // end::date_parsing[]
+        '''
+    }
+
+    void testCopyWith() {
+        assertScript '''
+        import static java.util.Calendar.*
+
+        // tag::date_copyWith[]
+        def newYear = Date.parse('yyyy-MM-dd', '2000-01-01')
+        def newYearsEve = newYear.copyWith(
+            year: 1999,
+            month: DECEMBER,
+            dayOfMonth: 31
+        )
+        assert newYearsEve[DAY_OF_WEEK] == FRIDAY
+        // end::date_copyWith[]
+        '''
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/subprojects/groovy-dateutil/src/test/java/groovy/DateTest.groovy
----------------------------------------------------------------------
diff --git a/subprojects/groovy-dateutil/src/test/java/groovy/DateTest.groovy b/subprojects/groovy-dateutil/src/test/java/groovy/DateTest.groovy
new file mode 100644
index 0000000..b014f53
--- /dev/null
+++ b/subprojects/groovy-dateutil/src/test/java/groovy/DateTest.groovy
@@ -0,0 +1,327 @@
+/*
+ *  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.
+ */
+package groovy
+
+import java.text.DateFormat
+import java.text.SimpleDateFormat
+
+import static java.util.Calendar.*
+
+class DateTest extends GroovyTestCase {
+    void testCalendarNextPrevious() {
+        TimeZone tz = TimeZone.getTimeZone('GMT+00')
+        Calendar c = getInstance(tz)
+        c[HOUR_OF_DAY] = 6
+        c[YEAR] = 2002
+        c[MONTH] = FEBRUARY
+        c[DATE] = 2
+        c.clearTime()
+        def formatter = new SimpleDateFormat('dd-MMM-yyyy', Locale.US)
+        formatter.calendar.timeZone = tz
+
+        assert formatter.format(c.previous().time) == '01-Feb-2002'
+        assert formatter.format(c.time) == '02-Feb-2002'
+        assert formatter.format(c.next().time) == '03-Feb-2002'
+        def dates = (c.previous()..c.next()).collect{ formatter.format(it.time) }
+        assert dates == ['01-Feb-2002', '02-Feb-2002', '03-Feb-2002']
+    }
+
+    void testDateNextPrevious() {
+        def tz = TimeZone.default
+        def x = new Date()
+        def y = x + 2
+        assert x < y
+        def crossedDaylightSavingBoundary = tz.inDaylightTime(x) ^ tz.inDaylightTime(y)
+        ++x
+        --y
+        if (!crossedDaylightSavingBoundary) assert x == y
+        x += 2
+        assert x > y
+    }
+
+    void testDateRange() {
+        def today = new Date()
+        def later = today + 3
+        def expected = [today, today + 1, today + 2, today + 3]
+        def list = []
+        for (d in today..later) {
+            list << d
+        }
+        assert list == expected
+    }
+
+    void testCalendarIndex() {
+        Calendar c = new GregorianCalendar(2002, FEBRUARY, 2)
+        assert c[MONTH] == FEBRUARY
+        assert c[DAY_OF_WEEK] == SATURDAY
+    }
+
+    void testDateIndex() {
+        Date d = new GregorianCalendar(2002, FEBRUARY, 2).time
+        assert d[MONTH] == FEBRUARY
+        assert d[DAY_OF_WEEK] == SATURDAY
+    }
+
+    void testGDKDateMethods() {
+        Locale defaultLocale = Locale.default
+        TimeZone defaultTZ = TimeZone.default
+        try {
+            Locale locale = Locale.GERMANY
+            Locale.setDefault locale // set this otherwise the test will fail if your locale isn't the same
+            TimeZone.setDefault TimeZone.getTimeZone('Europe/Berlin')
+
+            Date d = new Date(0)
+
+            assertEquals '1970-01-01', d.format('yyyy-MM-dd')
+            assertEquals '01/01/1970', d.format('dd/MM/yyyy', TimeZone.getTimeZone('GMT'))
+            assertEquals DateFormat.getDateInstance(DateFormat.SHORT, locale).format(d), d.dateString
+            assertEquals '01.01.70', d.dateString
+            assertEquals DateFormat.getTimeInstance(DateFormat.MEDIUM, locale).format(d), d.timeString
+            assertEquals '01:00:00', d.timeString
+            assertEquals DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, locale).format(d), d.dateTimeString
+        } finally {
+            Locale.default = defaultLocale
+            TimeZone.setDefault defaultTZ
+        }
+    }
+
+    void testStaticParse() {
+        TimeZone defaultTZ = TimeZone.default
+        try {
+            TimeZone.setDefault TimeZone.getTimeZone('Etc/GMT')
+
+            Date d = Date.parse('yy/MM/dd hh:mm:ss', '70/01/01 00:00:00')
+
+            assertEquals 0, d.time
+        } finally {
+            TimeZone.setDefault defaultTZ
+        }
+    }
+
+    void testParseWithTimeZone() {
+        TimeZone defaultTZ = TimeZone.default
+        try {
+            TimeZone.default = TimeZone.getTimeZone("GMT+05")
+            def tz = TimeZone.getTimeZone("GMT+03")
+
+            def newYear = Date.parse('yyyy-MM-dd', "2015-01-01", tz)
+
+            assert newYear.toString() == 'Thu Jan 01 02:00:00 GMT+05:00 2015'
+        } finally {
+            TimeZone.default = defaultTZ
+        }
+    }
+
+    void testRoundTrip() {
+        Date d = new Date()
+        String pattern = 'dd MMM yyyy, hh:mm:ss,SSS a z'
+        String out = d.format(pattern)
+
+        Date d2 = Date.parse(pattern, out)
+
+        assertEquals d.time, d2.time
+    }
+
+    void testCalendarTimeZone() {
+        Locale defaultLocale = Locale.default
+        TimeZone defaultTZ = TimeZone.default
+        try {
+            Locale locale = Locale.UK
+            Locale.setDefault locale // set this otherwise the test will fail if your locale isn't the same
+            TimeZone.setDefault TimeZone.getTimeZone('Etc/GMT')
+
+            def offset = 8
+            def notLocalTZ = TimeZone.getTimeZone("GMT-$offset")
+            Calendar cal = Calendar.getInstance(notLocalTZ)
+            def offsetHr = cal.format('HH') as int
+            def hr = cal.time.format('HH') as int
+            if (hr < offset) hr += 24 // if GMT hr has rolled over to next day
+
+            // offset should be 8 hours behind GMT:
+            assertEquals(offset, hr - offsetHr)
+        } finally {
+            Locale.default = defaultLocale
+            TimeZone.setDefault defaultTZ
+        }
+    }
+
+    static SimpleDateFormat f = new SimpleDateFormat('MM/dd/yyyy')
+
+    static java.sql.Date sqlDate(String s) {
+        return new java.sql.Date(f.parse(s).time)
+    }
+
+    void testMinusDates() {
+        assertEquals(10, f.parse("1/11/2007") - f.parse("1/1/2007"))
+        assertEquals(-10, f.parse("1/1/2007") - f.parse("1/11/2007"))
+        assertEquals(375, f.parse("1/11/2008") - f.parse("1/1/2007"))
+        assertEquals(356, f.parse("1/1/2008") - f.parse("1/10/2007"))
+        assertEquals(1, f.parse("7/12/2007") - f.parse("7/11/2007"))
+        assertEquals(0, f.parse("1/1/2007") - f.parse("1/1/2007"))
+        assertEquals(-1, f.parse("12/31/2007") - f.parse("1/1/2008"))
+        assertEquals(365, f.parse("1/1/2008") - f.parse("1/1/2007"))
+        assertEquals(36525, f.parse("1/1/2008") - f.parse("1/1/1908"))
+
+        assertEquals(1, sqlDate("7/12/2007") - f.parse("7/11/2007"))
+        assertEquals(0, sqlDate("1/1/2007") - sqlDate("1/1/2007"))
+        assertEquals(-1, f.parse("12/31/2007") - sqlDate("1/1/2008"))
+        assertEquals(365, sqlDate("1/1/2008") - sqlDate("1/1/2007"))
+        assertEquals(36525, f.parse("1/1/2008") - sqlDate("1/1/1908"))
+
+        Date d = f.parse("7/4/1776");
+        assertEquals(44, (d + 44) - d);
+
+        java.sql.Date sqld = sqlDate("7/4/1776");
+        assertEquals(-4444, (sqld - 4444) - sqld);
+    }
+
+    /** GROOVY-3374  */
+    void testClearTime() {
+        def now = new Date()
+        def calendarNow = Calendar.getInstance()
+
+        now.clearTime()
+        calendarNow.clearTime()
+
+        assert now == calendarNow.time
+
+        assert calendarNow.get(Calendar.HOUR) == 0
+        assert calendarNow.get(Calendar.MINUTE) == 0
+        assert calendarNow.get(Calendar.SECOND) == 0
+        assert calendarNow.get(Calendar.MILLISECOND) == 0
+    }
+
+    /** GROOVY-4789 */
+    void testStaticParseToStringDate() {
+        TimeZone tz = TimeZone.getDefault()
+        try {
+            TimeZone.setDefault(TimeZone.getTimeZone("GMT"))
+
+            Date date = new Date(0)
+            String toStringRepresentation = date.toString()
+
+            assert toStringRepresentation == "Thu Jan 01 00:00:00 GMT 1970"
+            assert date == Date.parseToStringDate(toStringRepresentation)
+        }
+        finally {
+            TimeZone.setDefault(tz)
+        }
+    }
+
+    void test_Upto_Date_ShouldExecuteClosureForEachDayUpToDate() {
+        Date startDate = new Date()
+        List expectedResults = [startDate, startDate + 1, startDate + 2]
+
+        List actualResults = []
+
+        startDate.upto(startDate + 2){
+            actualResults << it
+        }
+
+        assert actualResults == expectedResults
+    }
+
+    void test_upto_Date_ShouldNotAcceptToDatesLessThanStartDate() {
+        Date startDate = new Date()
+        Date toDate = new Date(startDate.getTime() - 1L)
+
+        shouldFail(GroovyRuntimeException) {
+            startDate.upto(toDate){}
+        }
+    }
+
+    void test_downto_Date_ShouldExecuteClosureForEachDayDownToDate() {
+        Date startDate = new Date()
+        List expectedResults = [startDate, startDate - 1, startDate - 2]
+
+        List actualResults = []
+        startDate.downto(startDate - 2){
+            actualResults << it
+        }
+
+        assert actualResults == expectedResults
+    }
+
+    void test_downto_Date_ShouldNotAcceptToDatesGreaterThanStartDate() {
+        Date startDate = new Date()
+        Date toDate = new Date(startDate.getTime() + 1L)
+
+        shouldFail(GroovyRuntimeException) {
+            startDate.downto(toDate){}
+        }
+    }
+
+    void test_upto_Calendar_ShouldExecuteClosureForEachDayUpToDate() {
+        Calendar startDate = Calendar.getInstance()
+        Calendar toDate = startDate.clone()
+        toDate.add(Calendar.DATE, 1)
+        List expectedResults = [startDate, toDate]
+
+        List actualResults = []
+        startDate.upto(toDate){
+            actualResults << it
+        }
+
+        assert actualResults == expectedResults
+    }
+
+    void test_upto_Calendar_ShouldNotAcceptToDatesLessThanStartDate() {
+        Calendar startDate = Calendar.getInstance()
+        Calendar toDate = startDate.clone()
+        toDate.add(Calendar.MILLISECOND, -1)
+
+        shouldFail(GroovyRuntimeException) {
+            startDate.upto(toDate){}
+        }
+    }
+
+    void test_downto_Calendar_ShouldExecuteClosureForEachDayDownToDate() {
+        Calendar startDate = Calendar.getInstance()
+        Calendar toDate = startDate.clone()
+        toDate.add(Calendar.DATE, -1)
+        List expectedResults = [startDate, toDate]
+
+        List actualResults = []
+        startDate.downto(toDate){
+            actualResults << it
+        }
+
+        assert actualResults == expectedResults
+    }
+
+    void test_downto_Calendar_ShouldNotAcceptToDatesGreaterThanStartDate() {
+        Calendar startDate = Calendar.getInstance()
+        Calendar toDate = startDate.clone()
+        toDate.add(Calendar.MILLISECOND, 1)
+
+        shouldFail(GroovyRuntimeException) {
+            startDate.downto(toDate){}
+        }
+    }
+
+    void testCopyWith() {
+        Date febOne1970 = new Date(70, 1, 1)
+        Date aprilSix = febOne1970.copyWith(dayOfMonth: 6, month: Calendar.APRIL)
+        assertEquals '1970-04-06', aprilSix.format('yyyy-MM-dd')
+        Map updates = [:]
+        updates[Calendar.DAY_OF_MONTH] = 4
+        Date aprilFour = aprilSix.copyWith(updates)
+        assertEquals '1970-04-04', aprilFour.format('yyyy-MM-dd')
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/subprojects/groovy-dateutil/src/test/java/org/apache/groovy/dateutil/extensions/DateUtilExtensionsTest.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-dateutil/src/test/java/org/apache/groovy/dateutil/extensions/DateUtilExtensionsTest.java b/subprojects/groovy-dateutil/src/test/java/org/apache/groovy/dateutil/extensions/DateUtilExtensionsTest.java
new file mode 100644
index 0000000..8cecc5b
--- /dev/null
+++ b/subprojects/groovy-dateutil/src/test/java/org/apache/groovy/dateutil/extensions/DateUtilExtensionsTest.java
@@ -0,0 +1,64 @@
+/*
+ *  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.
+ */
+
+package org.apache.groovy.dateutil.extensions;
+
+import org.junit.Test;
+
+import java.sql.Timestamp;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+import static org.junit.Assert.assertEquals;
+
+public class DateUtilExtensionsTest {
+    @Test
+    public void plus() throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
+        Date dec31 = sdf.parse("20171231");
+        assertEquals("20180101", sdf.format(DateUtilExtensions.plus(dec31, 1)));
+        assertEquals("20180101", sdf.format(DateUtilExtensions.plus(new Timestamp(dec31.getTime()), 1)));
+    }
+
+    @Test
+    public void minus() throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
+        Date jan01 = sdf.parse("20180101");
+        assertEquals("20171231", sdf.format(DateUtilExtensions.minus(jan01, 1)));
+        assertEquals("20171231", sdf.format(DateUtilExtensions.minus(new Timestamp(jan01.getTime()), 1)));
+    }
+
+    @Test
+    public void next() throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(sdf.parse("20171231"));
+        assertEquals("20180101", sdf.format(DateUtilExtensions.next(calendar).getTime()));
+    }
+
+    @Test
+    public void previous() throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(sdf.parse("20180101"));
+        assertEquals("20171231", sdf.format(DateUtilExtensions.previous(calendar).getTime()));
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/subprojects/groovy-json/build.gradle
----------------------------------------------------------------------
diff --git a/subprojects/groovy-json/build.gradle b/subprojects/groovy-json/build.gradle
index c96a723..c9bf97a 100644
--- a/subprojects/groovy-json/build.gradle
+++ b/subprojects/groovy-json/build.gradle
@@ -20,4 +20,5 @@ dependencies {
     compile rootProject
     testCompile project(':groovy-test')
     testRuntime project(':groovy-ant')
+    testCompile project(':groovy-dateutil')
 }


[07/14] groovy git commit: move some @author metnionds to pomconfigurer

Posted by pa...@apache.org.
move some @author metnionds to pomconfigurer


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/48b41cac
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/48b41cac
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/48b41cac

Branch: refs/heads/GROOVY_2_6_X
Commit: 48b41cac9ebfc34d2eada4fbc1aafb61cdf99d8a
Parents: 8d261b9
Author: paulk <pa...@asert.com.au>
Authored: Tue Mar 20 16:26:17 2018 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Thu Mar 22 00:41:47 2018 +1000

----------------------------------------------------------------------
 gradle/pomconfigurer.gradle                                   | 6 ++++++
 .../org/codehaus/groovy/runtime/DateTimeGroovyMethods.java    | 2 --
 .../codehaus/groovy/runtime/DefaultGroovyStaticMethods.java   | 7 -------
 .../groovy/runtime/DefaultGroovyStaticMethodsTest.java        | 5 +----
 4 files changed, 7 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/48b41cac/gradle/pomconfigurer.gradle
----------------------------------------------------------------------
diff --git a/gradle/pomconfigurer.gradle b/gradle/pomconfigurer.gradle
index 1234fbb..1bbfcc2 100644
--- a/gradle/pomconfigurer.gradle
+++ b/gradle/pomconfigurer.gradle
@@ -603,6 +603,12 @@ project.ext.pomConfigureClosureWithoutTweaks = {
             contributor {
                 name 'Graeme Rocher'
             }
+            contributor {
+                name 'Joe Wolf'
+            }
+            contributor {
+                name 'Kent Inge Fagerland Simonsen'
+            }
         }
         mailingLists {
             mailingList {

http://git-wip-us.apache.org/repos/asf/groovy/blob/48b41cac/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
index 529cbd0..93cecf5 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DateTimeGroovyMethods.java
@@ -37,8 +37,6 @@ import static java.time.temporal.ChronoUnit.YEARS;
 /**
  * This class defines new Groovy methods which appear on normal JDK
  * Date/Time API (java.time) classes inside the Groovy environment.
- *
- * @author Joe Wolf
  */
 public class DateTimeGroovyMethods {
 

http://git-wip-us.apache.org/repos/asf/groovy/blob/48b41cac/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
index 0ad2f33..ac69c59 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyStaticMethods.java
@@ -36,13 +36,6 @@ import java.util.stream.Collectors;
  * This class defines all the new static groovy methods which appear on normal
  * JDK classes inside the Groovy environment. Static methods are used with the
  * first parameter as the destination class.
- *
- * @author Guillaume Laforge
- * @author Dierk Koenig
- * @author Joachim Baumann
- * @author Paul King
- * @author Kent Inge Fagerland Simonsen
- * @author Joe Wolf
  */
 public class DefaultGroovyStaticMethods {
 

http://git-wip-us.apache.org/repos/asf/groovy/blob/48b41cac/src/test/org/codehaus/groovy/runtime/DefaultGroovyStaticMethodsTest.java
----------------------------------------------------------------------
diff --git a/src/test/org/codehaus/groovy/runtime/DefaultGroovyStaticMethodsTest.java b/src/test/org/codehaus/groovy/runtime/DefaultGroovyStaticMethodsTest.java
index 0a2edfe..dca8148 100644
--- a/src/test/org/codehaus/groovy/runtime/DefaultGroovyStaticMethodsTest.java
+++ b/src/test/org/codehaus/groovy/runtime/DefaultGroovyStaticMethodsTest.java
@@ -21,13 +21,10 @@ package org.codehaus.groovy.runtime;
 import groovy.util.GroovyTestCase;
 
 /**
- * @author Kent Inge Fagerland Simonsen
+ * Tests for DefaultGroovyStaticMethods
  */
 public class DefaultGroovyStaticMethodsTest extends GroovyTestCase {
 
-    /**
-     *Tests System.currentTimeSeconds()
-     */
     public void testCurrentTimeSeconds() {
 	long timeMillis = System.currentTimeMillis();
         long timeSeconds = DefaultGroovyStaticMethods.currentTimeSeconds(null);


[11/14] groovy git commit: move datetime extensions to their own module

Posted by pa...@apache.org.
http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/src/spec/doc/working-with-datetime-types.adoc
----------------------------------------------------------------------
diff --git a/src/spec/doc/working-with-datetime-types.adoc b/src/spec/doc/working-with-datetime-types.adoc
deleted file mode 100644
index 6567154..0000000
--- a/src/spec/doc/working-with-datetime-types.adoc
+++ /dev/null
@@ -1,341 +0,0 @@
-//////////////////////////////////////////
-
-  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
-:gdk: http://www.groovy-lang.org/gdk.html[Groovy development kit]
-:java-util-list: http://docs.oracle.com/javase/8/docs/api/java/util/List.html[java.util.List]
-:java-time-types: `java.time` types
-
-Groovy's syntax and extension methods within the {gdk} provide conveniences for using
-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 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 date 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 the value of `end` 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]
--------------------------------------

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy
----------------------------------------------------------------------
diff --git a/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy b/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy
deleted file mode 100644
index 99e7f8d..0000000
--- a/src/spec/test/gdk/WorkingWithDateTimeTypesTest.groovy
+++ /dev/null
@@ -1,222 +0,0 @@
-package gdk
-
-import java.time.*
-import java.time.chrono.JapaneseDate
-import java.time.temporal.ChronoField
-import java.time.temporal.ChronoUnit
-
-class WorkingWithDateTimeTypesTest extends GroovyTestCase {
-
-    void testParsing() {
-        // tag::static_parsing[]
-        def date = LocalDate.parse('Jun 3, 04','MMM d, yy')
-        assert date == LocalDate.of(2004, Month.JUNE, 3)
-
-        def time = LocalTime.parse('4:45','H:mm')
-        assert time == LocalTime.of(4, 45, 0)
-
-        def offsetTime = OffsetTime.parse('09:47:51-1234', 'HH:mm:ssZ')
-        assert offsetTime == OffsetTime.of(9, 47, 51, 0, ZoneOffset.ofHoursMinutes(-12, -34))
-
-        def dateTime =  ZonedDateTime.parse('2017/07/11 9:47PM Pacific Standard Time', 'yyyy/MM/dd h:mma zzzz')
-        assert dateTime == ZonedDateTime.of(
-                               LocalDate.of(2017, 7, 11),
-                               LocalTime.of(21, 47, 0),
-                               ZoneId.of('America/Los_Angeles')
-                           )
-        // end::static_parsing[]
-    }
-
-    void testRange() {
-        // tag::date_ranges[]
-        def start = LocalDate.now()
-        def end = start + 6 // 6 days later
-        (start..end).each { date ->
-            println date.dayOfWeek
-        }
-        // end::date_ranges[]
-    }
-
-    void testUptoDownto() {
-        // tag::date_upto_date[]
-        def start = LocalDate.now()
-        def end = start + 6 // 6 days later
-        start.upto(end) { date ->
-            println date.dayOfWeek
-        }
-        // end::date_upto_date[]
-    }
-
-    void testUptoCustomUnit() {
-        // tag::date_upto_date_by_months[]
-        def start = LocalDate.of(2018, Month.MARCH, 2)
-        def end = start + 1 // 1 day later
-
-        int iterationCount = 0
-        start.upto(end, ChronoUnit.MONTHS) { date ->
-            println date
-            ++iterationCount
-        }
-
-        assert iterationCount == 1
-        // end::date_upto_date_by_months[]
-    }
-
-    void testPlusMinusWithTemporalAmounts() {
-        // tag::plus_minus_period[]
-        def aprilFools = LocalDate.of(2018, Month.APRIL, 1)
-
-        def nextAprilFools = aprilFools + Period.ofDays(365) // add 365 days
-        assert nextAprilFools.year == 2019
-
-        def idesOfMarch = aprilFools - Period.ofDays(17) // subtract 17 days
-        assert idesOfMarch.dayOfMonth == 15
-        assert idesOfMarch.month == Month.MARCH
-        // end::plus_minus_period[]
-    }
-
-    void testLocalDatePlusMinusInteger() {
-        def aprilFools = LocalDate.of(2018, Month.APRIL, 1)
-
-        // tag::localdate_plus_minus_integer[]
-        def nextAprilFools = aprilFools + 365 // add 365 days
-        def idesOfMarch = aprilFools - 17 // subtract 17 days
-        // end::localdate_plus_minus_integer[]
-
-        assert nextAprilFools.year == 2019
-        assert idesOfMarch.dayOfMonth == 15
-        assert idesOfMarch.month == Month.MARCH
-    }
-
-    void testLocalTimePlusMinusInteger() {
-        // tag::localtime_plus_minus_integer[]
-        def mars = LocalTime.of(12, 34, 56) // 12:34:56 pm
-
-        def thirtySecondsToMars = mars - 30 // go back 30 seconds
-        assert thirtySecondsToMars.second == 26
-        // end::localtime_plus_minus_integer[]
-    }
-
-    void testNextPrevious() {
-        // tag::next_previous[]
-        def year = Year.of(2000)
-        --year // decrement by one year
-        assert year.value == 1999
-
-        def offsetTime = OffsetTime.of(0, 0, 0, 0, ZoneOffset.UTC) // 00:00:00.000 UTC
-        offsetTime++ // increment by one second
-        assert offsetTime.second == 1
-        // end::next_previous[]
-    }
-
-    void testMultiplyDivide() {
-        // tag::multiply_divide[]
-        def period = Period.ofMonths(1) * 2 // a 1-month period times 2
-        assert period.months == 2
-
-        def duration = Duration.ofSeconds(10) / 5// a 10-second duration divided by 5
-        assert duration.seconds == 2
-        // end::multiply_divide[]
-    }
-
-    void testNegation() {
-        // tag::duration_negation[]
-        def duration = Duration.ofSeconds(-15)
-        def negated = -duration
-        assert negated.seconds == 15
-        // end::duration_negation[]
-    }
-
-    void testPropertyNotation() {
-        // tag::property_notation[]
-        def date = LocalDate.of(2018, Month.MARCH, 12)
-        assert date[ChronoField.YEAR] == 2018
-        assert date[ChronoField.MONTH_OF_YEAR] == Month.MARCH.value
-        assert date[ChronoField.DAY_OF_MONTH] == 12
-        assert date[ChronoField.DAY_OF_WEEK] == DayOfWeek.MONDAY.value
-
-        def period = Period.ofYears(2).withMonths(4).withDays(6)
-        assert period[ChronoUnit.YEARS] == 2
-        assert period[ChronoUnit.MONTHS] == 4
-        assert period[ChronoUnit.DAYS] == 6
-        // end::property_notation[]
-    }
-
-    void testLeftShift() {
-        // tag::leftshift_operator[]
-        MonthDay monthDay = Month.JUNE << 3 // June 3rd
-        LocalDate date = monthDay << Year.of(2015) // 3-Jun-2015
-        LocalDateTime dateTime = date << LocalTime.NOON // 3-Jun-2015 @ 12pm
-        OffsetDateTime offsetDateTime = dateTime << ZoneOffset.ofHours(-5) // 3-Jun-2015 @ 12pm UTC-5
-        // end::leftshift_operator[]
-        // tag::leftshift_operator_reflexive[]
-        def year = Year.of(2000)
-        def month = Month.DECEMBER
-
-        YearMonth a = year << month
-        YearMonth b = month << year
-        assert a == b
-        // end::leftshift_operator_reflexive[]
-    }
-
-    void testRightShift() {
-        // tag::rightshift_operator_period[]
-        def newYears = LocalDate.of(2018, Month.JANUARY, 1)
-        def aprilFools = LocalDate.of(2018, Month.APRIL, 1)
-
-        def period = newYears >> aprilFools
-        assert period instanceof Period
-        assert period.months == 3
-        // end::rightshift_operator_period[]
-
-        // tag::rightshift_operator_duration[]
-        def duration = LocalTime.NOON >> (LocalTime.NOON + 30)
-        assert duration instanceof Duration
-        assert duration.seconds == 30
-        // end::rightshift_operator_duration[]
-
-        // tag::rightshift_operator_negative[]
-        def decade = Year.of(2010) >> Year.of(2000)
-        assert decade.years == -10
-        // end::rightshift_operator_negative[]
-    }
-
-    void testToDateAndToCalendar() {
-        // tag::todate_tocalendar[]
-        // LocalDate to java.util.Date
-        def valentines = LocalDate.of(2018, Month.FEBRUARY, 14)
-        assert valentines.toDate().format('MMMM dd, yyyy') == 'February 14, 2018'
-
-        // LocalTime to java.util.Date
-        def noon = LocalTime.of(12, 0, 0)
-        assert noon.toDate().format('HH:mm:ss') == '12:00:00'
-
-        // ZoneId to java.util.TimeZone
-        def newYork = ZoneId.of('America/New_York')
-        assert newYork.toTimeZone() == TimeZone.getTimeZone('America/New_York')
-
-        // ZonedDateTime to java.util.Calendar
-        def valAtNoonInNY = ZonedDateTime.of(valentines, noon, newYork)
-        assert valAtNoonInNY.toCalendar().getTimeZone().toZoneId() == newYork
-        // end::todate_tocalendar[]
-    }
-
-    void testConvertToJSR310Types() {
-        // tag::to_jsr310_types[]
-        Date legacy = Date.parse('yyyy-MM-dd HH:mm:ss.SSS', '2010-04-03 10:30:58.999')
-
-        assert legacy.toLocalDate() == LocalDate.of(2010, 4, 3)
-        assert legacy.toLocalTime() == LocalTime.of(10, 30, 58, 999_000_000) // 999M ns = 999ms
-        assert legacy.toOffsetTime().hour == 10
-        assert legacy.toYear() == Year.of(2010)
-        assert legacy.toMonth() == Month.APRIL
-        assert legacy.toDayOfWeek() == DayOfWeek.SATURDAY
-        assert legacy.toMonthDay() == MonthDay.of(Month.APRIL, 3)
-        assert legacy.toYearMonth() == YearMonth.of(2010, Month.APRIL)
-        assert legacy.toLocalDateTime().year == 2010
-        assert legacy.toOffsetDateTime().dayOfMonth == 3
-        assert legacy.toZonedDateTime().zone == ZoneId.systemDefault()
-        // end::to_jsr310_types[]
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/src/test/groovy/DateTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/groovy/DateTest.groovy b/src/test/groovy/DateTest.groovy
deleted file mode 100644
index 3c2034d..0000000
--- a/src/test/groovy/DateTest.groovy
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- *  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.
- */
-package groovy
-
-import java.text.SimpleDateFormat
-
-import static java.util.Calendar.*
-
-class DateTest extends GroovyTestCase {
-    void testCalendarNextPrevious() {
-        TimeZone tz = TimeZone.getTimeZone('GMT+00')
-        Calendar c = getInstance(tz)
-        c[HOUR_OF_DAY] = 6
-        c[YEAR] = 2002
-        c[MONTH] = FEBRUARY
-        c[DATE] = 2
-        c.clearTime()
-        def formatter = new SimpleDateFormat('dd-MMM-yyyy', Locale.US)
-        formatter.calendar.timeZone = tz
-
-        assert formatter.format(c.previous().time) == '01-Feb-2002'
-        assert formatter.format(c.time) == '02-Feb-2002'
-        assert formatter.format(c.next().time) == '03-Feb-2002'
-        def dates = (c.previous()..c.next()).collect{ formatter.format(it.time) }
-        assert dates == ['01-Feb-2002', '02-Feb-2002', '03-Feb-2002']
-    }
-
-    void testDateNextPrevious() {
-        def tz = TimeZone.default
-        def x = new Date()
-        def y = x + 2
-        assert x < y
-        def crossedDaylightSavingBoundary = tz.inDaylightTime(x) ^ tz.inDaylightTime(y)
-        ++x
-        --y
-        if (!crossedDaylightSavingBoundary) assert x == y
-        x += 2
-        assert x > y
-    }
-
-    void testDateRange() {
-        def today = new Date()
-        def later = today + 3
-        def expected = [today, today + 1, today + 2, today + 3]
-        def list = []
-        for (d in today..later) {
-            list << d
-        }
-        assert list == expected
-    }
-
-    void testCalendarIndex() {
-        Calendar c = new GregorianCalendar(2002, FEBRUARY, 2)
-        assert c[MONTH] == FEBRUARY
-        assert c[DAY_OF_WEEK] == SATURDAY
-    }
-
-    void testDateIndex() {
-        Date d = new GregorianCalendar(2002, FEBRUARY, 2).time
-        assert d[MONTH] == FEBRUARY
-        assert d[DAY_OF_WEEK] == SATURDAY
-    }
-}

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/src/test/groovy/DateTimeTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/groovy/DateTimeTest.groovy b/src/test/groovy/DateTimeTest.groovy
deleted file mode 100644
index db2e266..0000000
--- a/src/test/groovy/DateTimeTest.groovy
+++ /dev/null
@@ -1,821 +0,0 @@
-package groovy
-
-import java.text.SimpleDateFormat
-import java.time.*
-import java.time.chrono.JapaneseDate
-import java.time.temporal.ChronoField
-import java.time.temporal.ChronoUnit
-
-class DateTimeTest extends GroovyTestCase {
-
-    void testDurationPlusMinusPositiveNegative() {
-        def duration = Duration.ofSeconds(10)
-        def longer = duration + 5
-        def shorter = duration - 5
-
-        assert longer.seconds == 15
-        assert shorter.seconds == 5
-        assert (++longer).seconds == 16
-        assert (--shorter).seconds == 4
-    }
-
-    void testInstantPlusMinusPositiveNegative() {
-        def epoch = Instant.ofEpochMilli(0)
-
-        def twoSecPastEpoch = epoch + 2
-        def oneSecPastEpoch = twoSecPastEpoch - 1
-
-        assert oneSecPastEpoch.epochSecond == 1
-        assert twoSecPastEpoch.epochSecond == 2
-        assert (++twoSecPastEpoch).epochSecond == 3
-        assert (--oneSecPastEpoch).epochSecond == 0
-    }
-
-    void testLocalDatePlusMinusPositiveNegative() {
-        def epoch = LocalDate.of(1970, Month.JANUARY, 1)
-
-        def twoDaysPastEpoch = epoch + 2
-        def oneDayPastEpoch = twoDaysPastEpoch - 1
-
-        assert oneDayPastEpoch.dayOfMonth == 2
-        assert twoDaysPastEpoch.dayOfMonth == 3
-        assert (++twoDaysPastEpoch).dayOfMonth == 4
-        assert (--oneDayPastEpoch).dayOfMonth == 1
-    }
-
-    void testLocalDateTimePlusMinusPositiveNegative() {
-        def epoch = LocalDateTime.of(1970, Month.JANUARY, 1, 0, 0, 0, 0)
-
-        def twoSecsPastEpoch = epoch + 2
-        def oneSecPastEpoch = twoSecsPastEpoch - 1
-
-        assert oneSecPastEpoch.second == 1
-        assert twoSecsPastEpoch.second == 2
-        assert (++twoSecsPastEpoch).second == 3
-        assert (--oneSecPastEpoch).second == 0
-    }
-
-    void testLocalTimePlusMinusPositiveNegative() {
-        def epoch = LocalTime.of(0, 0, 0, 0)
-
-        def twoSecsPastEpoch = epoch + 2
-        def oneSecPastEpoch = twoSecsPastEpoch - 1
-
-        assert oneSecPastEpoch.second == 1
-        assert twoSecsPastEpoch.second == 2
-        assert (++twoSecsPastEpoch).second == 3
-        assert (--oneSecPastEpoch).second == 0
-    }
-
-    void testOffsetDateTimePlusMinusPositiveNegative() {
-        def epoch = OffsetDateTime.of(LocalDateTime.of(1970, Month.JANUARY, 1, 0, 0, 0, 0),
-                ZoneOffset.ofHours(0))
-
-        def twoSecsPastEpoch = epoch + 2
-        def oneSecPastEpoch = twoSecsPastEpoch - 1
-
-        assert oneSecPastEpoch.second == 1
-        assert twoSecsPastEpoch.second == 2
-        assert (++twoSecsPastEpoch).second == 3
-        assert (--oneSecPastEpoch).second == 0
-    }
-
-    void testOffsetTimePlusMinusPositiveNegative() {
-        def epoch = OffsetTime.of(LocalTime.of(0, 0, 0, 0),
-                ZoneOffset.ofHours(0))
-
-        def twoSecsPastEpoch = epoch + 2
-        def oneSecPastEpoch = twoSecsPastEpoch - 1
-
-        assert oneSecPastEpoch.second == 1
-        assert twoSecsPastEpoch.second == 2
-        assert (++twoSecsPastEpoch).second == 3
-        assert (--oneSecPastEpoch).second == 0
-    }
-
-    void testPeriodPlusMinusPositiveNegative() {
-        def fortnight = Period.ofDays(14)
-
-        def fortnightAndTwoDays = fortnight + 2
-        def fortnightAndOneDay = fortnightAndTwoDays - 1
-
-        assert fortnightAndOneDay.days == 15
-        assert fortnightAndTwoDays.days == 16
-        assert (++fortnightAndTwoDays).days == 17
-        assert (--fortnightAndOneDay).days == 14
-    }
-
-    void testYearPlusMinusPositiveNegative() {
-        def epoch = Year.of(1970)
-
-        def twoYearsAfterEpoch = epoch + 2
-        def oneYearAfterEpoch = twoYearsAfterEpoch - 1
-
-        assert oneYearAfterEpoch.value == 1971
-        assert twoYearsAfterEpoch.value == 1972
-        assert (++twoYearsAfterEpoch).value == 1973
-        assert (--oneYearAfterEpoch).value == 1970
-    }
-
-    void testYearMonthPlusMinusPositiveNegative() {
-        def epoch = YearMonth.of(1970, Month.JANUARY)
-
-        def twoMonthsAfterEpoch = epoch + 2
-        def oneMonthAfterEpoch = twoMonthsAfterEpoch - 1
-
-        assert oneMonthAfterEpoch.month == Month.FEBRUARY
-        assert twoMonthsAfterEpoch.month == Month.MARCH
-        assert (++twoMonthsAfterEpoch).month == Month.APRIL
-        assert (--oneMonthAfterEpoch).month == Month.JANUARY
-    }
-
-    void testZonedDateTimePlusMinusPositiveNegative() {
-        def epoch = ZonedDateTime.of(LocalDateTime.of(1970, Month.JANUARY, 1, 0, 0, 0, 0),
-                ZoneId.systemDefault())
-
-        def twoSecsPastEpoch = epoch + 2
-        def oneSecPastEpoch = twoSecsPastEpoch - 1
-
-        assert oneSecPastEpoch.second == 1
-        assert twoSecsPastEpoch.second == 2
-        assert (++twoSecsPastEpoch).second == 3
-        assert (--oneSecPastEpoch).second == 0
-    }
-
-    void testDayOfWeekPlusMinus() {
-        def mon = DayOfWeek.MONDAY
-
-        assert mon + 4 == DayOfWeek.FRIDAY
-        assert mon - 4 == DayOfWeek.THURSDAY
-    }
-
-    void testMonthPlusMinus() {
-        def jan = Month.JANUARY
-
-        assert jan + 4 == Month.MAY
-        assert jan - 4 == Month.SEPTEMBER
-    }
-
-    void testDurationPositiveNegative() {
-        def positiveDuration = Duration.ofSeconds(3)
-        assert (-positiveDuration).seconds == -3
-
-        def negativeDuration = Duration.ofSeconds(-5)
-        assert (+negativeDuration).seconds == 5
-    }
-
-    void testDurationMultiplyDivide() {
-        def duration = Duration.ofSeconds(60)
-
-        assert (duration / 2).seconds == 30
-        assert (duration * 2).seconds == 120
-    }
-
-    void testDurationIsPositiveIsNonnegativeIsNonpositive() {
-        def pos = Duration.ofSeconds(10)
-        assert pos.isPositive() == true
-        assert pos.isNonpositive() == false
-        assert pos.isNonnegative() == true
-
-        def neg = Duration.ofSeconds(-10)
-        assert neg.isPositive() == false
-        assert neg.isNonpositive() == true
-        assert neg.isNonnegative() == false
-
-        assert Duration.ZERO.isPositive() == false
-        assert Duration.ZERO.isNonpositive() == true
-        assert Duration.ZERO.isNonnegative() == true
-    }
-
-    void testPeriodPositiveNegative() {
-        def positivePeriod = Period.of(1,2,3)
-        Period madeNegative = -positivePeriod
-        assert madeNegative.years == -1 : "All Period fields should be made negative"
-        assert madeNegative.months == -2
-        assert madeNegative.days == -3
-
-        def negativePeriod = Period.of(-1,2,-3)
-        Period madePositive = +negativePeriod
-        assert madePositive.years == 1 : "Negative Period fields should be made positive"
-        assert madePositive.months == 2 : "Positive Period fields should remain positive"
-        assert madePositive.days == 3
-    }
-
-    void testPeriodMultiply() {
-        def period = Period.of(1,1,1)
-        Period doublePeriod = period * 2
-        assert doublePeriod.years == 2
-        assert doublePeriod.months == 2
-        assert doublePeriod.days == 2
-    }
-
-    void testPeriodIsPositiveIsNonnegativeIsNonpositive() {
-        def pos = Period.ofDays(10)
-        assert pos.isPositive() == true
-        assert pos.isNonpositive() == false
-        assert pos.isNonnegative() == true
-
-        def neg = Period.ofDays(-10)
-        assert neg.isPositive() == false
-        assert neg.isNonpositive() == true
-        assert neg.isNonnegative() == false
-
-        assert Period.ZERO.isPositive() == false
-        assert Period.ZERO.isNonpositive() == true
-        assert Period.ZERO.isNonnegative() == true
-    }
-
-    void testTemporalGetAt() {
-        def epoch = Instant.ofEpochMilli(0)
-        assert epoch[ChronoField.INSTANT_SECONDS] == 0
-    }
-
-    void testTemporalAmountGetAt() {
-        def duration = Duration.ofHours(10)
-        assert duration[ChronoUnit.SECONDS] == 36_000
-    }
-
-    void testZoneOffsetGetAt() {
-        def offset = ZoneOffset.ofTotalSeconds(360)
-        assert offset[ChronoField.OFFSET_SECONDS] == 360
-    }
-
-    void testTemporalRightShift() {
-        def epoch = Instant.ofEpochMilli(0)
-        def dayAfterEpoch = epoch + (60 * 60 * 24)
-        Duration instantDuration = epoch >> dayAfterEpoch
-        assert instantDuration == Duration.ofDays(1)
-    }
-
-    void testLocalDateRightShift() {
-        def localDate1 = LocalDate.of(2000, Month.JANUARY, 1)
-        def localDate2 = localDate1.plusYears(2)
-        Period localDatePeriod = localDate1 >> localDate2
-        assert localDatePeriod.years == 2
-    }
-
-    void testYearRightShift() {
-        def year1 = Year.of(2000)
-        def year2 = Year.of(2018)
-        Period yearPeriod = year1 >> year2
-        assert yearPeriod.years == 18
-    }
-
-    void testYearMonthRightShift() {
-        def yearMonth1 = YearMonth.of(2018, Month.JANUARY)
-        def yearMonth2 = YearMonth.of(2018, Month.MARCH)
-        Period yearMonthPeriod = yearMonth1 >> yearMonth2
-        assert yearMonthPeriod.months == 2
-    }
-
-    void testRightShiftDifferentTypes() {
-        try {
-            LocalDate.now() >> LocalTime.now()
-            fail('Should not be able to use right shift on different Temporal types.')
-        } catch (e) {
-            assert e instanceof GroovyRuntimeException
-        }
-    }
-
-    void testUptoDifferentTypes() {
-        try {
-            LocalDate.now().upto(JapaneseDate.now().plus(1, ChronoUnit.MONTHS)) { d -> }
-            fail('Cannot use upto() with two different Temporal types.')
-        } catch (e) {
-            assert e instanceof GroovyRuntimeException
-        }
-    }
-
-    void testDowntoDifferentTypes() {
-        try {
-            LocalDate.now().downto(JapaneseDate.now().minus(1, ChronoUnit.MONTHS)) { d -> }
-            fail('Cannot use downto() with two different argument types.')
-        } catch (e) {
-            assert e instanceof GroovyRuntimeException
-        }
-    }
-
-    void testUptoSelfWithDefaultUnit() {
-        def epoch = Instant.ofEpochMilli(0)
-
-        int iterations = 0
-        epoch.upto(epoch) {
-            ++iterations
-            assert it == epoch: 'upto closure should be provided with arg'
-        }
-        assert iterations == 1: 'Iterating upto same value should call closure once'
-    }
-
-    void testDowntoSelfWithDefaultUnit() {
-        def epoch = Instant.ofEpochMilli(0)
-        int iterations = 0
-        epoch.downto(epoch) {
-            ++iterations
-            assert it == epoch: 'downto closure should be provided with arg'
-        }
-        assert iterations == 1: 'Iterating downto same value should call closure once'
-    }
-
-    void testUptoWithSecondsDefaultUnit() {
-        def epoch = Instant.ofEpochMilli(0)
-
-        int iterations = 0
-        Instant end = null
-        epoch.upto(epoch + 1) {
-            ++iterations
-            end = it
-        }
-        assert iterations == 2: 'Iterating upto Temporal+1 value should call closure twice'
-        assert end.epochSecond == 1: 'Unexpected upto final value'
-    }
-
-    void testDowntoWithSecondsDefaultUnit() {
-        def epoch = Instant.ofEpochMilli(0)
-
-        int iterations = 0
-        Instant end = null
-        epoch.downto(epoch - 1) {
-            ++iterations
-            end = it
-        }
-        assert iterations == 2 : 'Iterating downto Temporal+1 value should call closure twice'
-        assert end.epochSecond == -1 : 'Unexpected downto final value'
-    }
-
-    void testUptoWithYearsDefaultUnit() {
-        def endYear = null
-        Year.of(1970).upto(Year.of(1971)) { year -> endYear = year }
-        assert endYear.value == 1971
-    }
-
-    void testDowntoWithYearsDefaultUnit() {
-        def endYear = null
-        Year.of(1971).downto(Year.of(1970)) { year -> endYear = year }
-        assert endYear.value == 1970
-    }
-
-    void testUptoWithMonthsDefaultUnit() {
-        def endYearMonth = null
-        YearMonth.of(1970, Month.JANUARY).upto(YearMonth.of(1970, Month.FEBRUARY)) { yearMonth ->
-            endYearMonth = yearMonth
-        }
-        assert endYearMonth.month == Month.FEBRUARY
-    }
-
-    void testDowntoWithMonthsDefaultUnit() {
-        def endYearMonth = null
-        YearMonth.of(1970, Month.FEBRUARY).downto(YearMonth.of(1970, Month.JANUARY)) { yearMonth ->
-            endYearMonth = yearMonth
-        }
-        assert endYearMonth.month == Month.JANUARY
-    }
-
-    void testUptoWithDaysDefaultUnit() {
-        def endLocalDate = null
-        LocalDate.of(1970, Month.JANUARY, 1).upto(LocalDate.of(1970, Month.JANUARY, 2)) {  localDate ->
-            endLocalDate = localDate
-        }
-        assert endLocalDate.dayOfMonth == 2
-    }
-
-    void testDowntoWithDaysDefaultUnit() {
-        def endLocalDate = null
-        LocalDate.of(1970, Month.JANUARY, 2).downto(LocalDate.of(1970, Month.JANUARY, 1)) {  localDate ->
-            endLocalDate = localDate
-        }
-        assert endLocalDate.dayOfMonth == 1
-    }
-
-    void testUptoWithIllegalReversedArguments() {
-        def epoch = Instant.ofEpochMilli(0)
-        try {
-            epoch.upto(epoch - 1) {
-                fail('upto() should fail when passed earlier arg')
-            }
-        } catch (GroovyRuntimeException e) {
-        }
-    }
-
-    void testDowntoWithIllegalReversedArguments() {
-        def epoch = Instant.ofEpochMilli(0)
-        try {
-            epoch.downto(epoch + 1) {
-                fail('downto() should fail when passed earlier arg')
-            }
-        } catch (GroovyRuntimeException e) {}
-    }
-
-    void testUptoSelfWithCustomUnit() {
-        def today = LocalDate.now()
-
-        int iterations = 0
-        today.upto(today, ChronoUnit.MONTHS) {
-            ++iterations
-            assert it == today: 'upto closure should be provided with arg'
-        }
-        assert iterations == 1: 'Iterating upto same value should call closure once'
-    }
-
-    void testDowntoSelfWithCustomUnit() {
-        def today = LocalDate.now()
-
-        int iterations = 0
-        today.downto(today, ChronoUnit.MONTHS) {
-            ++iterations
-            assert it == today: 'downto closure should be provided with arg'
-        }
-        assert iterations == 1: 'Iterating downto same value should call closure once'
-    }
-
-    void testUptoWithCustomUnit() {
-        LocalDateTime from = LocalDateTime.of(2018, Month.FEBRUARY, 11, 22, 9, 34)
-        // one second beyond one iteration
-        LocalDateTime to = from.plusDays(1).plusSeconds(1)
-
-        int iterations = 0
-        LocalDateTime end = null
-        from.upto(to, ChronoUnit.DAYS) {
-            ++iterations
-            end = it
-        }
-        assert iterations == 2
-        assert end.dayOfMonth == 12: "Upto should have iterated by DAYS twice"
-    }
-
-    void testDowntoWithCustomUnit() {
-        LocalDateTime from = LocalDateTime.of(2018, Month.FEBRUARY, 11, 22, 9, 34)
-        // one day beyond one iteration
-        LocalDateTime to = from.minusYears(1).minusDays(1)
-
-        int iterations = 0
-        LocalDateTime end = null
-        from.downto(to, ChronoUnit.YEARS) {
-            ++iterations
-            end = it
-        }
-        assert iterations == 2
-        assert end.year == 2017 : "Downto should have iterated by YEARS twice"
-    }
-
-    void testInstantToDateToCalendar() {
-        def epoch = Instant.ofEpochMilli(0).plusNanos(999_999)
-
-        def date = epoch.toDate()
-        def cal = epoch.toCalendar()
-        assert cal.time == date
-        def sdf = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss.SSS')
-        sdf.timeZone = TimeZone.getTimeZone('GMT')
-        assert sdf.format(date) == '1970-01-01 00:00:00.000'
-    }
-
-    void testLocalDateToDateToCalendar() {
-        def ld = LocalDate.of(2018, Month.FEBRUARY, 12)
-
-        Calendar cal = ld.toCalendar()
-        assert cal.get(Calendar.YEAR) == 2018
-        assert cal.get(Calendar.MONTH) == Calendar.FEBRUARY
-        assert cal.get(Calendar.DAY_OF_MONTH) == 12
-        assert cal.timeZone.getID() == TimeZone.default.getID()
-
-        Date date = ld.toDate()
-        assert date.format('yyyy-MM-dd') == '2018-02-12'
-    }
-
-    void testLocalDateTimeToDateToCalendar() {
-        def ldt = LocalDateTime.of(2018, Month.FEBRUARY, 12, 22, 26, 30, 123_999_999)
-
-        Calendar cal = ldt.toCalendar()
-        assert cal.get(Calendar.YEAR) == 2018
-        assert cal.get(Calendar.MONTH) == Calendar.FEBRUARY
-        assert cal.get(Calendar.DAY_OF_MONTH) == 12
-        assert cal.get(Calendar.HOUR_OF_DAY) == 22
-        assert cal.get(Calendar.MINUTE) == 26
-        assert cal.get(Calendar.SECOND) == 30
-        assert cal.get(Calendar.MILLISECOND) == 123
-        assert cal.timeZone.getID() == TimeZone.default.getID()
-
-        Date date = ldt.toDate()
-        assert date.format('yyyy-MM-dd HH:mm:ss.SSS') == '2018-02-12 22:26:30.123'
-    }
-
-    void testLocalTimeToDateToCalendar() {
-        def today = Calendar.instance
-        def lt = LocalTime.of(22, 38, 20, 9_999_999)
-
-        Calendar cal = lt.toCalendar()
-        assert cal.get(Calendar.YEAR) == today.get(Calendar.YEAR) : 'LocalTime.toCalendar() should have current year'
-        assert cal.get(Calendar.MONTH) == today.get(Calendar.MONTH) : 'LocalTime.toCalendar() should have current month'
-        assert cal.get(Calendar.DAY_OF_MONTH) == today.get(Calendar.DAY_OF_MONTH) : 'LocalTime.toCalendar() should have current day'
-        assert cal.get(Calendar.HOUR_OF_DAY) == 22
-        assert cal.get(Calendar.MINUTE) == 38
-        assert cal.get(Calendar.SECOND) == 20
-        assert cal.get(Calendar.MILLISECOND) == 9
-        assert cal.timeZone.getID() == TimeZone.default.getID()
-
-        Date date = lt.toDate()
-        assert date.format('HH:mm:ss.SSS') == '22:38:20.009'
-    }
-
-    void testOffsetDateTimeToDateToCalendar() {
-        def ld = LocalDate.of(2018, Month.FEBRUARY, 12)
-        def lt = LocalTime.of(22, 46, 10, 16_000_001)
-        def offset = ZoneOffset.ofHours(-5)
-        def odt = OffsetDateTime.of(ld, lt, offset)
-
-        Calendar cal = odt.toCalendar()
-        assert cal.get(Calendar.YEAR) == 2018
-        assert cal.get(Calendar.MONTH) == Calendar.FEBRUARY
-        assert cal.get(Calendar.DAY_OF_MONTH) == 12
-        assert cal.get(Calendar.HOUR_OF_DAY) == 22
-        assert cal.get(Calendar.MINUTE) == 46
-        assert cal.get(Calendar.SECOND) == 10
-        assert cal.get(Calendar.MILLISECOND) == 16
-        assert cal.timeZone.getOffset(System.currentTimeMillis()) == -5 * 60 * 60 * 1000
-
-        Date date = odt.toDate()
-        def sdf = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss.SSS Z')
-        sdf.timeZone = cal.timeZone
-        assert sdf.format(date) == '2018-02-12 22:46:10.016 -0500'
-    }
-
-    void testOffsetTimeToDateToCalendar() {
-        def lt = LocalTime.of(22, 53, 2, 909_900_009)
-        def offset = ZoneOffset.ofHours(-4)
-        def ot = OffsetTime.of(lt, offset)
-        Calendar today = Calendar.getInstance(TimeZone.getTimeZone('GMT-4'))
-
-        Calendar cal = ot.toCalendar()
-        assert cal.get(Calendar.YEAR) == today.get(Calendar.YEAR) : 'OffsetTime.toCalendar() should have current year'
-        assert cal.get(Calendar.MONTH) == today.get(Calendar.MONTH) : 'OffsetTime.toCalendar() should have current month'
-        assert cal.get(Calendar.DAY_OF_MONTH) == today.get(Calendar.DAY_OF_MONTH) : 'OffsetTime.toCalendar() should have current day'
-        assert cal.get(Calendar.HOUR_OF_DAY) == 22
-        assert cal.get(Calendar.MINUTE) == 53
-        assert cal.get(Calendar.SECOND) == 2
-        assert cal.get(Calendar.MILLISECOND) == 909
-        assert cal.timeZone.getOffset(System.currentTimeMillis()) == -4 * 60 * 60 * 1000
-
-        Date date = ot.toDate()
-        def sdf = new SimpleDateFormat('HH:mm:ss.SSS Z')
-        sdf.timeZone = cal.timeZone
-        assert sdf.format(date) == '22:53:02.909 -0400'
-    }
-
-    void testZonedDateTimeToDateToCalendar() {
-        def ldt = LocalDateTime.of(2018, Month.FEBRUARY, 13, 20, 33, 57)
-        def zoneId = ZoneId.ofOffset('GMT', ZoneOffset.ofHours(3))
-        def zdt = ZonedDateTime.of(ldt, zoneId)
-
-        Calendar cal = zdt.toCalendar()
-        assert cal.get(Calendar.YEAR) == 2018
-        assert cal.get(Calendar.MONTH) == Calendar.FEBRUARY
-        assert cal.get(Calendar.DAY_OF_MONTH) == 13
-        assert cal.get(Calendar.HOUR_OF_DAY) == 20
-        assert cal.get(Calendar.MINUTE) == 33
-        assert cal.get(Calendar.SECOND) == 57
-        assert cal.get(Calendar.MILLISECOND) == 0
-        assert cal.timeZone.getOffset(System.currentTimeMillis()) == 3 * 60 * 60 * 1000
-
-        Date date = zdt.toDate()
-        def sdf = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss.SSS Z')
-        sdf.timeZone = cal.timeZone
-        assert sdf.format(date) == '2018-02-13 20:33:57.000 +0300'
-    }
-
-    void testZoneOffsetExtensionProperties() {
-        def offset = ZoneOffset.ofHoursMinutesSeconds(3,4,5)
-        assert offset.hours == 3
-        assert offset.minutes == 4
-        assert offset.seconds == 5
-
-        def negOffset = ZoneOffset.ofHoursMinutesSeconds(-1, -2, -3)
-        assert negOffset.hours == -1
-        assert negOffset.minutes == -2
-        assert negOffset.seconds == -3
-    }
-
-    void testZoneOffsetToZimeZone() {
-        TimeZone utcTz = ZoneOffset.UTC.toTimeZone()
-        assert utcTz.getID() == 'GMT'
-
-        TimeZone noSecsTz = ZoneOffset.ofHoursMinutes(1, 30).toTimeZone()
-        assert noSecsTz.getID() == 'GMT+01:30'
-
-        TimeZone secsTz = ZoneOffset.ofHoursMinutesSeconds(-4, -15, -30).toTimeZone()
-        assert secsTz.getID() == 'GMT-04:15'
-    }
-
-    void testZoneIdExtensionProperties() {
-        def offset = ZoneOffset.ofHours(7)
-        def zoneId = ZoneId.ofOffset('GMT', offset)
-
-        assert zoneId.offset.totalSeconds == offset.totalSeconds
-        assert zoneId.getOffset(Instant.now()).totalSeconds == offset.totalSeconds
-        assert zoneId.shortName == 'GMT+07:00'
-        assert zoneId.fullName == 'GMT+07:00'
-
-        ZoneId ny = ZoneId.of('America/New_York')
-        assert ny.getShortName(Locale.US) == 'ET'
-        assert ny.getFullName(Locale.US) == 'Eastern Time'
-    }
-
-    void testZoneIdToTimeZone() {
-        ZoneId ny = ZoneId.of('America/New_York')
-
-        assert ny.toTimeZone() == TimeZone.getTimeZone(ny)
-    }
-
-    void testYearExtensionProperties() {
-        def year = Year.of(2009)
-        assert year.era == 1
-        assert year.yearOfEra == 2009
-    }
-
-    void testDayOfWeekExtensionProperties() {
-        assert DayOfWeek.SUNDAY.weekend
-        assert DayOfWeek.MONDAY.weekday
-    }
-
-    void testYear_Month_leftShift() {
-        def a = Year.now()
-        def b = Month.JULY
-
-        YearMonth x = a << b
-        YearMonth y = b << a
-        assert x == y
-    }
-
-    void testYear_MonthDay_leftShift() {
-        def a = Year.now()
-        def b = MonthDay.now()
-
-        LocalDate x = a << b
-        LocalDate y = b << a
-        assert x == y
-    }
-
-    void testMonthDay_leftShift() {
-        LocalDate d = MonthDay.of(Month.FEBRUARY, 13) << 2018
-        assert d.year == 2018
-        assert d.month == Month.FEBRUARY
-        assert d.dayOfMonth == 13
-    }
-
-    void testMonth_leftShift() {
-        MonthDay md = Month.JANUARY << 10
-        assert md.month == Month.JANUARY
-        assert md.dayOfMonth == 10
-    }
-
-    void testLocalDate_LocalTime_leftShift() {
-        def a = LocalDate.now()
-        def b = LocalTime.now()
-
-        LocalDateTime x = a << b
-        LocalDateTime y = b << a
-        assert x == y
-    }
-
-    void testLocalDate_OffsetTime_leftShift() {
-        def a = LocalDate.now()
-        def b = OffsetTime.now()
-
-        OffsetDateTime x = a << b
-        OffsetDateTime y = b << a
-        assert x == y
-    }
-
-    void testLocalDateTime_ZoneOffset_leftShift() {
-        def a = LocalDateTime.now()
-        def b = ZoneOffset.ofHours(5)
-
-        OffsetDateTime x = a << b
-        OffsetDateTime y = b << a
-        assert x == y
-    }
-
-    void testLocalDateTime_ZoneId_leftShift() {
-        def a = LocalDateTime.now()
-        def b = ZoneId.systemDefault()
-
-        ZonedDateTime x = a << b
-        ZonedDateTime y = b << a
-        assert x == y
-    }
-
-    void testLocalTime_ZoneOffset_leftShift() {
-        def a = LocalTime.now()
-        def b = ZoneOffset.ofHours(5)
-
-        OffsetTime x = a << b
-        OffsetTime y = b << a
-        assert x == y
-    }
-
-    void testLocalDateTimeClearTime() {
-        def d = LocalDateTime.of(LocalDate.now(), LocalTime.of(8, 9, 10, 100_032))
-        d = d.clearTime()
-
-        assert d.hour == 0
-        assert d.minute == 0
-        assert d.second == 0
-        assert d.nano == 0
-    }
-
-    void testOffsetDateTimeClearTime() {
-        def offset = ZoneOffset.ofHours(-1)
-        def d = OffsetDateTime.of(LocalDate.now(), LocalTime.of(8, 9, 10, 100_032), offset)
-        d = d.clearTime()
-
-        assert d.hour == 0
-        assert d.minute == 0
-        assert d.second == 0
-        assert d.nano == 0
-        assert d.offset == offset : 'cleartTime() should not change offset'
-    }
-
-    void testZonedDateTimeClearTime() {
-        def zone =  ZoneId.of('America/New_York')
-        def d = ZonedDateTime.of(LocalDate.now(), LocalTime.of(8, 9, 10, 100_032), zone)
-        d = d.clearTime()
-
-        assert d.hour == 0
-        assert d.minute == 0
-        assert d.second == 0
-        assert d.nano == 0
-        assert d.zone == zone : 'cleartTime() should not change zone'
-    }
-
-    void testFormatByPattern() {
-        def zone =  ZoneId.of('America/New_York')
-        def offset = ZoneOffset.ofHours(2)
-
-        LocalDate ld = LocalDate.of(2018, Month.FEBRUARY, 13)
-        LocalTime lt = LocalTime.of(3,4,5,6_000_000)
-        LocalDateTime ldt = LocalDateTime.of(ld, lt)
-        OffsetTime ot = OffsetTime.of(lt, offset)
-        OffsetDateTime odt = OffsetDateTime.of(ldt, offset)
-        ZonedDateTime zdt = ZonedDateTime.of(ldt, zone)
-
-        assert ld.format('yyyy-MM-dd') == '2018-02-13'
-        assert lt.format('HH:mm:ss.SSS') == '03:04:05.006'
-        assert ldt.format('yyyy-MM-dd HH:mm:ss.SSS') == '2018-02-13 03:04:05.006'
-        assert ot.format('HH:mm:ss.SSS Z') == '03:04:05.006 +0200'
-        assert odt.format('yyyy-MM-dd HH:mm:ss.SSS Z') == '2018-02-13 03:04:05.006 +0200'
-        assert zdt.format('yyyy-MM-dd HH:mm:ss.SSS VV') == '2018-02-13 03:04:05.006 America/New_York'
-    }
-
-    void testLocalDateParse() {
-        LocalDate ld = LocalDate.parse('2018-02-15', 'yyyy-MM-dd')
-        assert [ld.year, ld.month, ld.dayOfMonth] == [2018, Month.FEBRUARY, 15]
-    }
-
-    void testLocalDateTimeParse() {
-        LocalDateTime ldt = LocalDateTime.parse('2018-02-15 21:43:03.002', 'yyyy-MM-dd HH:mm:ss.SSS')
-        assert [ldt.year, ldt.month, ldt.dayOfMonth] == [2018, Month.FEBRUARY, 15]
-        assert [ldt.hour, ldt.minute, ldt.second] == [21, 43, 03]
-        assert ldt.nano == 2 * 1e6
-    }
-
-    void testLocalTimeParse() {
-        LocalTime lt = LocalTime.parse('21:43:03.002', 'HH:mm:ss.SSS')
-        assert [lt.hour, lt.minute, lt.second] == [21, 43, 03]
-        assert lt.nano == 2 * 1e6
-    }
-
-    void testOffsetDateTimeParse() {
-        OffsetDateTime odt = OffsetDateTime.parse('2018-02-15 21:43:03.002 -00', 'yyyy-MM-dd HH:mm:ss.SSS X')
-        assert [odt.year, odt.month, odt.dayOfMonth] == [2018, Month.FEBRUARY, 15]
-        assert [odt.hour, odt.minute, odt.second] == [21, 43, 03]
-        assert odt.nano == 2 * 1e6
-        assert odt.offset.totalSeconds == 0
-    }
-
-    void testOffsetTimeParse() {
-        OffsetTime ot = OffsetTime.parse('21:43:03.002 -00', 'HH:mm:ss.SSS X')
-        assert [ot.hour, ot.minute, ot.second] == [21, 43, 03]
-        assert ot.nano == 2 * 1e6
-        assert ot.offset.totalSeconds == 0
-    }
-
-    void testZonedDateTimeParse() {
-        ZonedDateTime zdt = ZonedDateTime.parse('2018-02-15 21:43:03.002 UTC', 'yyyy-MM-dd HH:mm:ss.SSS z')
-        assert [zdt.year, zdt.month, zdt.dayOfMonth] == [2018, Month.FEBRUARY, 15]
-        assert [zdt.hour, zdt.minute, zdt.second] == [21, 43, 03]
-        assert zdt.nano == 2 * 1e6
-    }
-
-    void testPeriodBetweenYears() {
-        def period = Period.between(Year.of(2000), Year.of(2010))
-        assert period.years == 10
-        assert period.months == 0
-        assert period.days == 0
-    }
-
-    void testPeriodBetweenYearMonths() {
-        def period = Period.between(YearMonth.of(2018, Month.MARCH), YearMonth.of(2016, Month.APRIL))
-
-        assert period.years == -1
-        assert period.months == -11
-        assert period.days == 0
-    }
-}

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/src/test/java/org/codehaus/groovy/runtime/DateGroovyMethodsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/codehaus/groovy/runtime/DateGroovyMethodsTest.java b/src/test/java/org/codehaus/groovy/runtime/DateGroovyMethodsTest.java
deleted file mode 100644
index c182b12..0000000
--- a/src/test/java/org/codehaus/groovy/runtime/DateGroovyMethodsTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- *  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.
- */
-
-package org.codehaus.groovy.runtime;
-
-import org.junit.Test;
-
-import java.sql.Timestamp;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.time.*;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.TimeZone;
-
-import static org.junit.Assert.assertEquals;
-
-public class DateGroovyMethodsTest {
-    @Test
-    public void minus() throws ParseException {
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
-        assertEquals("20171231", sdf.format(DateGroovyMethods.minus(sdf.parse("20180101"), 1)));
-    }
-
-    @Test
-    public void plus() throws ParseException {
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
-        assertEquals("20180101", sdf.format(DateGroovyMethods.plus(new Timestamp(sdf.parse("20171231").getTime()), 1)));
-    }
-
-    @Test
-    public void next() throws ParseException {
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
-        Calendar calendar = Calendar.getInstance();
-        calendar.setTime(sdf.parse("20171231"));
-        assertEquals("20180101", sdf.format(DateGroovyMethods.next(calendar).getTime()));
-    }
-
-    @Test
-    public void previous() throws ParseException {
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
-        Calendar calendar = Calendar.getInstance();
-        calendar.setTime(sdf.parse("20180101"));
-        assertEquals("20171231", sdf.format(DateGroovyMethods.previous(calendar).getTime()));
-    }
-
-    @Test
-    public void calendarConversionsDefaultTimeZone() throws ParseException {
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HHmmss SSS");
-        Calendar calendar = Calendar.getInstance();
-        calendar.setTime(sdf.parse("20180115 153256 001"));
-
-        LocalDate expectedLocalDate = LocalDate.of(2018, Month.JANUARY, 15);
-        LocalTime expectedLocalTime = LocalTime.of(15, 32, 56, 1_000_000);
-        LocalDateTime expectedLocalDateTime = LocalDateTime.of(expectedLocalDate, expectedLocalTime);
-
-        assertEquals("DayOfWeek", DayOfWeek.MONDAY, DateGroovyMethods.toDayOfWeek(calendar));
-        assertEquals("Month", Month.JANUARY, DateGroovyMethods.toMonth(calendar));
-        assertEquals("MonthDay", MonthDay.of(Month.JANUARY, 15), DateGroovyMethods.toMonthDay(calendar));
-        assertEquals("YearMonth", YearMonth.of(2018, Month.JANUARY), DateGroovyMethods.toYearMonth(calendar));
-        assertEquals("Year", Year.of(2018), DateGroovyMethods.toYear(calendar));
-        assertEquals("LocalDate", expectedLocalDate, DateGroovyMethods.toLocalDate(calendar));
-        assertEquals("LocalTime", expectedLocalTime, DateGroovyMethods.toLocalTime(calendar));
-        assertEquals("LocalDateTime", expectedLocalDateTime, DateGroovyMethods.toLocalDateTime(calendar));
-        assertEquals("OffsetTime", expectedLocalTime, DateGroovyMethods.toOffsetTime(calendar).toLocalTime());
-        assertEquals("OffsetDateTime", expectedLocalDateTime,
-                DateGroovyMethods.toOffsetDateTime(calendar).toLocalDateTime());
-        assertEquals("ZonedDateTime", expectedLocalDateTime,
-                DateGroovyMethods.toZonedDateTime(calendar).toLocalDateTime());
-    }
-
-    @Test
-    public void calendarConversionsDifferingTimeZones() throws ParseException {
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HHmmss SSS");
-        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC+0"));
-        calendar.setTime(sdf.parse("20180115 153256 001"));
-    }
-
-    @Test
-    public void sameCalendarAndDateConvertIdentically() throws ParseException {
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd HHmmss SSS");
-        Date date = sdf.parse("20180115 153256 001");
-        Calendar calendar = Calendar.getInstance();
-        calendar.setTime(date);
-
-        assertEquals("DayOfWeek", DateGroovyMethods.toDayOfWeek(calendar), DateGroovyMethods.toDayOfWeek(date));
-        assertEquals("Month", DateGroovyMethods.toMonth(calendar), DateGroovyMethods.toMonth(date));
-        assertEquals("MonthDay", DateGroovyMethods.toMonthDay(calendar), DateGroovyMethods.toMonthDay(date));
-        assertEquals("YearMonth", DateGroovyMethods.toYearMonth(calendar), DateGroovyMethods.toYearMonth(date));
-        assertEquals("Year", DateGroovyMethods.toYear(calendar), DateGroovyMethods.toYear(date));
-        assertEquals("LocalDate", DateGroovyMethods.toLocalDate(calendar), DateGroovyMethods.toLocalDate(date));
-        assertEquals("LocalTime", DateGroovyMethods.toLocalTime(calendar), DateGroovyMethods.toLocalTime(date));
-        assertEquals("LocalDateTime", DateGroovyMethods.toLocalDate(calendar), DateGroovyMethods.toLocalDate(date));
-        assertEquals("OffsetTime", DateGroovyMethods.toOffsetTime(calendar), DateGroovyMethods.toOffsetTime(date));
-        assertEquals("OffsetDateTime",
-                DateGroovyMethods.toOffsetDateTime(calendar), DateGroovyMethods.toOffsetDateTime(date));
-        assertEquals("ZonedDateTime",
-                DateGroovyMethods.toZonedDateTime(calendar), DateGroovyMethods.toZonedDateTime(date));
-    }
-}

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/src/test/org/codehaus/groovy/runtime/DateGDKTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/org/codehaus/groovy/runtime/DateGDKTest.groovy b/src/test/org/codehaus/groovy/runtime/DateGDKTest.groovy
deleted file mode 100644
index 2c27878..0000000
--- a/src/test/org/codehaus/groovy/runtime/DateGDKTest.groovy
+++ /dev/null
@@ -1,275 +0,0 @@
-/*  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.
- */
-package org.codehaus.groovy.runtime
-
-import java.text.DateFormat
-import java.text.SimpleDateFormat
-
-/**
- * @author tnichols
- * @author Paul King
- */
-class DateGDKTest extends GroovyTestCase {
-
-    void testGDKDateMethods() {
-        Locale defaultLocale = Locale.default
-        TimeZone defaultTZ = TimeZone.default
-        try {
-            Locale locale = Locale.GERMANY
-            Locale.setDefault locale // set this otherwise the test will fail if your locale isn't the same
-            TimeZone.setDefault TimeZone.getTimeZone('Europe/Berlin')
-            
-            Date d = new Date(0)
-            
-            assertEquals '1970-01-01', d.format('yyyy-MM-dd')
-            assertEquals '01/01/1970', d.format('dd/MM/yyyy', TimeZone.getTimeZone('GMT'))
-            assertEquals DateFormat.getDateInstance(DateFormat.SHORT, locale).format(d), d.dateString
-            assertEquals '01.01.70', d.dateString
-            assertEquals DateFormat.getTimeInstance(DateFormat.MEDIUM, locale).format(d), d.timeString
-            assertEquals '01:00:00', d.timeString
-            assertEquals DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, locale).format(d), d.dateTimeString
-        } finally {
-            Locale.default = defaultLocale
-            TimeZone.setDefault defaultTZ
-        }
-    }
-
-    void testStaticParse() {
-        TimeZone defaultTZ = TimeZone.default
-        try {
-            TimeZone.setDefault TimeZone.getTimeZone('Etc/GMT')
-
-            Date d = Date.parse('yy/MM/dd hh:mm:ss', '70/01/01 00:00:00')
-            
-            assertEquals 0, d.time
-        } finally {
-            TimeZone.setDefault defaultTZ
-        }
-    }
-
-    void testParseWithTimeZone() {
-        TimeZone defaultTZ = TimeZone.default
-        try {
-            TimeZone.default = TimeZone.getTimeZone("GMT+05")
-            def tz = TimeZone.getTimeZone("GMT+03")
-            
-            def newYear = Date.parse('yyyy-MM-dd', "2015-01-01", tz)
-            
-            assert newYear.toString() == 'Thu Jan 01 02:00:00 GMT+05:00 2015'
-        } finally {
-            TimeZone.default = defaultTZ
-        }
-    }
-
-    void testRoundTrip() {
-        Date d = new Date()
-        String pattern = 'dd MMM yyyy, hh:mm:ss,SSS a z'
-        String out = d.format(pattern)
-
-        Date d2 = Date.parse(pattern, out)
-
-        assertEquals d.time, d2.time
-    }
-
-    void testCalendarTimeZone() {
-        Locale defaultLocale = Locale.default
-        TimeZone defaultTZ = TimeZone.default
-        try {
-            Locale locale = Locale.UK
-            Locale.setDefault locale // set this otherwise the test will fail if your locale isn't the same
-            TimeZone.setDefault TimeZone.getTimeZone('Etc/GMT')
-
-            def offset = 8
-            def notLocalTZ = TimeZone.getTimeZone("GMT-$offset")
-            Calendar cal = Calendar.getInstance(notLocalTZ)
-            def offsetHr = cal.format('HH') as int
-            def hr = cal.time.format('HH') as int
-            if (hr < offset) hr += 24 // if GMT hr has rolled over to next day
-            
-            // offset should be 8 hours behind GMT:
-            assertEquals(offset, hr - offsetHr)
-        } finally {
-            Locale.default = defaultLocale
-            TimeZone.setDefault defaultTZ
-        }
-    }
-
-    static SimpleDateFormat f = new SimpleDateFormat('MM/dd/yyyy')
-
-    static java.sql.Date sqlDate(String s) {
-        return new java.sql.Date(f.parse(s).time)
-    }
-
-    void testMinusDates() {
-        assertEquals(10, f.parse("1/11/2007") - f.parse("1/1/2007"))
-        assertEquals(-10, f.parse("1/1/2007") - f.parse("1/11/2007"))
-        assertEquals(375, f.parse("1/11/2008") - f.parse("1/1/2007"))
-        assertEquals(356, f.parse("1/1/2008") - f.parse("1/10/2007"))
-        assertEquals(1, f.parse("7/12/2007") - f.parse("7/11/2007"))
-        assertEquals(0, f.parse("1/1/2007") - f.parse("1/1/2007"))
-        assertEquals(-1, f.parse("12/31/2007") - f.parse("1/1/2008"))
-        assertEquals(365, f.parse("1/1/2008") - f.parse("1/1/2007"))
-        assertEquals(36525, f.parse("1/1/2008") - f.parse("1/1/1908"))
-
-        assertEquals(1, sqlDate("7/12/2007") - f.parse("7/11/2007"))
-        assertEquals(0, sqlDate("1/1/2007") - sqlDate("1/1/2007"))
-        assertEquals(-1, f.parse("12/31/2007") - sqlDate("1/1/2008"))
-        assertEquals(365, sqlDate("1/1/2008") - sqlDate("1/1/2007"))
-        assertEquals(36525, f.parse("1/1/2008") - sqlDate("1/1/1908"))
-
-        Date d = f.parse("7/4/1776");
-        assertEquals(44, (d + 44) - d);
-
-        java.sql.Date sqld = sqlDate("7/4/1776");
-        assertEquals(-4444, (sqld - 4444) - sqld);
-    }
-
-    /** GROOVY-3374  */
-    void testClearTime() {
-        def now = new Date()
-        def calendarNow = Calendar.getInstance()
-
-        now.clearTime()
-        calendarNow.clearTime()
-
-        assert now == calendarNow.time
-
-        assert calendarNow.get(Calendar.HOUR) == 0
-        assert calendarNow.get(Calendar.MINUTE) == 0
-        assert calendarNow.get(Calendar.SECOND) == 0
-        assert calendarNow.get(Calendar.MILLISECOND) == 0
-    }
-
-    /** GROOVY-4789 */
-    void testStaticParseToStringDate() {
-        TimeZone tz = TimeZone.getDefault()
-        try {
-            TimeZone.setDefault(TimeZone.getTimeZone("GMT"))
-
-            Date date = new Date(0)
-            String toStringRepresentation = date.toString()
-
-            assert toStringRepresentation == "Thu Jan 01 00:00:00 GMT 1970"
-            assert date == Date.parseToStringDate(toStringRepresentation)
-        }
-        finally {
-            TimeZone.setDefault(tz)
-        }
-    }
-
-    void test_Upto_Date_ShouldExecuteClosureForEachDayUpToDate() {
-        Date startDate = new Date()
-        List expectedResults = [startDate, startDate + 1, startDate + 2]
-
-        List actualResults = []
-
-        startDate.upto(startDate + 2){
-            actualResults << it
-        }
-
-        assert actualResults == expectedResults
-    }
-
-    void test_upto_Date_ShouldNotAcceptToDatesLessThanStartDate() {
-        Date startDate = new Date()
-        Date toDate = new Date(startDate.getTime() - 1L)
-
-        shouldFail(GroovyRuntimeException) {
-            startDate.upto(toDate){}
-        }
-    }
-
-    void test_downto_Date_ShouldExecuteClosureForEachDayDownToDate() {
-        Date startDate = new Date()
-        List expectedResults = [startDate, startDate - 1, startDate - 2]
-
-        List actualResults = []
-        startDate.downto(startDate - 2){
-            actualResults << it
-        }
-
-        assert actualResults == expectedResults
-    }
-
-    void test_downto_Date_ShouldNotAcceptToDatesGreaterThanStartDate() {
-        Date startDate = new Date()
-        Date toDate = new Date(startDate.getTime() + 1L)
-
-        shouldFail(GroovyRuntimeException) {
-            startDate.downto(toDate){}
-        }
-    }
-
-    void test_upto_Calendar_ShouldExecuteClosureForEachDayUpToDate() {
-        Calendar startDate = Calendar.getInstance()
-        Calendar toDate = startDate.clone()
-        toDate.add(Calendar.DATE, 1)
-        List expectedResults = [startDate, toDate]
-
-        List actualResults = []
-        startDate.upto(toDate){
-            actualResults << it
-        }
-
-        assert actualResults == expectedResults
-    }
-
-    void test_upto_Calendar_ShouldNotAcceptToDatesLessThanStartDate() {
-        Calendar startDate = Calendar.getInstance()
-        Calendar toDate = startDate.clone()
-        toDate.add(Calendar.MILLISECOND, -1)
-
-        shouldFail(GroovyRuntimeException) {
-            startDate.upto(toDate){}
-        }
-    }
-
-    void test_downto_Calendar_ShouldExecuteClosureForEachDayDownToDate() {
-        Calendar startDate = Calendar.getInstance()
-        Calendar toDate = startDate.clone()
-        toDate.add(Calendar.DATE, -1)
-        List expectedResults = [startDate, toDate]
-
-        List actualResults = []
-        startDate.downto(toDate){
-            actualResults << it
-        }
-
-        assert actualResults == expectedResults
-    }
-
-    void test_downto_Calendar_ShouldNotAcceptToDatesGreaterThanStartDate() {
-        Calendar startDate = Calendar.getInstance()
-        Calendar toDate = startDate.clone()
-        toDate.add(Calendar.MILLISECOND, 1)
-
-        shouldFail(GroovyRuntimeException) {
-            startDate.downto(toDate){}
-        }
-    }
-
-    void testCopyWith() {
-        Date febOne1970 = new Date(70, 1, 1)
-        Date aprilSix = febOne1970.copyWith(dayOfMonth: 6, month: Calendar.APRIL)
-        assertEquals '1970-04-06', aprilSix.format('yyyy-MM-dd')
-        Map updates = [:]
-        updates[Calendar.DAY_OF_MONTH] = 4
-        Date aprilFour = aprilSix.copyWith(updates)
-        assertEquals '1970-04-04', aprilFour.format('yyyy-MM-dd')
-    }
-}

http://git-wip-us.apache.org/repos/asf/groovy/blob/718a820a/subprojects/groovy-datetime/build.gradle
----------------------------------------------------------------------
diff --git a/subprojects/groovy-datetime/build.gradle b/subprojects/groovy-datetime/build.gradle
new file mode 100644
index 0000000..92326c7
--- /dev/null
+++ b/subprojects/groovy-datetime/build.gradle
@@ -0,0 +1,29 @@
+/*
+ *  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.
+ */
+dependencies {
+    compile rootProject
+    testCompile project(':groovy-test')
+    testCompile project(':groovy-dateutil')
+}
+
+task moduleDescriptor(type: org.codehaus.groovy.gradle.WriteExtensionDescriptorTask) {
+    extensionClasses = 'org.apache.groovy.datetime.extensions.DateTimeExtensions'
+    staticExtensionClasses = 'org.apache.groovy.datetime.extensions.DateTimeStaticExtensions'
+}
+compileJava.dependsOn moduleDescriptor