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
