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:58 UTC

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

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));
+    }
 }