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