From bc257aa260e751d30ef4ab31f451d88ad674a673 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 22 Mar 2018 10:44:02 +0100 Subject: [PATCH] DATAMONGO-1834 - Polishing. Remove DateFactory and split up tests. Introduce dedicated Timezone abstraction and update existing factories to apply the timezone if appropriate. Update builders and align code style. Original Pull Request: #539 --- .../AbstractAggregationExpression.java | 92 +- .../mongodb/core/aggregation/DateFactory.java | 62 - .../core/aggregation/DateOperators.java | 3485 ++++++----------- .../aggregation/DateOperatorsUnitTests.java | 899 ----- .../ProjectionOperationUnitTests.java | 293 +- src/main/asciidoc/reference/mongodb.adoc | 2 +- 6 files changed, 1629 insertions(+), 3204 deletions(-) delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateFactory.java delete mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/DateOperatorsUnitTests.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java index 544e1c8dc..573eed4a1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java @@ -27,6 +27,7 @@ import org.springframework.util.ObjectUtils; /** * @author Christoph Strobl + * @author Matt Morrissette * @since 1.10 */ abstract class AbstractAggregationExpression implements AggregationExpression { @@ -45,6 +46,7 @@ abstract class AbstractAggregationExpression implements AggregationExpression { return toDocument(this.value, context); } + @SuppressWarnings("unchecked") public Document toDocument(Object value, AggregationOperationContext context) { return new Document(getMongoMethod(), unpack(value, context)); } @@ -63,11 +65,13 @@ abstract class AbstractAggregationExpression implements AggregationExpression { if (value instanceof AggregationExpression) { return ((AggregationExpression) value).toDocument(context); - } else if (value instanceof DateFactory) { - return ((DateFactory) value).currentDate(); - } else if (value instanceof Field) { + } + + if (value instanceof Field) { return context.getReference((Field) value).toString(); - } else if (value instanceof List) { + } + + if (value instanceof List) { List sourceList = (List) value; List mappedList = new ArrayList<>(sourceList.size()); @@ -75,16 +79,21 @@ abstract class AbstractAggregationExpression implements AggregationExpression { sourceList.stream().map((item) -> unpack(item, context)).forEach(mappedList::add); return mappedList; - } else if (value instanceof java.util.Map) { - Document dbo = new Document(); - ((Map) value).forEach((k, v) -> dbo.put(k, unpack(v, context))); - return dbo; + } + + if (value instanceof Map) { + + Document targetDocument = new Document(); + + Map sourceMap = (Map) value; + sourceMap.forEach((k, v) -> targetDocument.append(k, unpack(v, context))); + + return targetDocument; } return value; } - @SuppressWarnings({ "unchecked", "rawtypes" }) protected List append(Object value) { if (this.value instanceof List) { @@ -110,13 +119,12 @@ abstract class AbstractAggregationExpression implements AggregationExpression { if (!(this.value instanceof java.util.Map)) { throw new IllegalArgumentException("o_O"); } - java.util.Map clone = new LinkedHashMap((java.util.Map) this.value); + java.util.Map clone = new LinkedHashMap<>((java.util.Map) this.value); clone.put(key, value); return clone; } - @SuppressWarnings({ "unchecked", "rawtypes" }) protected List values() { if (value instanceof List) { @@ -125,7 +133,67 @@ abstract class AbstractAggregationExpression implements AggregationExpression { if (value instanceof java.util.Map) { return new ArrayList(((java.util.Map) value).values()); } - return new ArrayList(Collections.singletonList(value)); + return new ArrayList<>(Collections.singletonList(value)); + } + + /** + * Get the value at a given index. + * + * @param index + * @param + * @return + * @since 2.1 + */ + protected T get(int index) { + return (T) values().get(index); + } + + /** + * Get the value for a given key. + * + * @param key + * @param + * @return + * @since 2.1 + */ + protected T get(Object key) { + + if (!(this.value instanceof java.util.Map)) { + throw new IllegalArgumentException("o_O"); + } + + return (T) ((java.util.Map) this.value).get(key); + } + + /** + * Get the argument map. + * + * @since 2.1 + * @return + */ + protected java.util.Map argumentMap() { + + if (!(this.value instanceof java.util.Map)) { + throw new IllegalArgumentException("o_O"); + } + + return Collections.unmodifiableMap((java.util.Map) value); + } + + /** + * Check if the given key is available. + * + * @param key + * @return + * @since 2.1 + */ + protected boolean contains(Object key) { + + if (!(this.value instanceof java.util.Map)) { + return false; + } + + return ((java.util.Map) this.value).containsKey(key); } protected abstract String getMongoMethod(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateFactory.java deleted file mode 100644 index a3c44860f..000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateFactory.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2018. the original author or authors. - * - * Licensed 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.springframework.data.mongodb.core.aggregation; - -import java.util.Date; - -/** - * Used for {@link DateOperators} related functions to access the current date - * - * @since 2.1 - * @author Matt Morrissette - */ -@FunctionalInterface -public interface DateFactory { - - /** - * @author Matt Morrissette - * @param currentDate - * @return A date factory that always uses the given date as the current date. Primary used in testing and mock - * scenarios. - */ - public static DateFactory fixedDate(final Object currentDate) { - return () -> currentDate; - } - - /** - * DateFactory that uses the date as it is on the local server - */ - public static final DateFactory LOCAL_DATE_FACTORY = Date::new; - - /** - * Should return an object that is serializable by the BSON encoder and would resolve to a BSON Date when evaluated. - *

- * This includes - *

    - *
  • {@link java.util.Date}
  • - *
  • {@link java.util.Calendar}
  • - *
  • {@link java.time.Instant}
  • - *
  • {@link java.time.ZonedDateTime}
  • - *
  • {@link java.lang.Long}
  • - *
  • org.joda.time.AbstractInstant
  • - *
- * - * @author Matt Morrissette - * @return - */ - public Object currentDate(); - -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java index 48a76d2b2..a3fd38a0a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java @@ -15,25 +15,16 @@ */ package org.springframework.data.mongodb.core.aggregation; -import static org.springframework.data.mongodb.core.aggregation.ConditionalOperators.*; - +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; -import org.bson.Document; -import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators.ArithmeticOperatorFactory; -import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.*; -import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; /** * Gateway to {@literal Date} aggregation operations. - *

- * Prior to Mongo 3.6, all Date operations were in the UTC timezone
- * New in Mongo 3.6 is support for timezone conversion on all aggregation operations. This is a breaking change - * and using any of the aggregation methods with a 'timezone' attribute on a Mongo server prior to 3.6 will cause - * errors. * * @author Christoph Strobl * @author Matt Morrissette @@ -42,257 +33,157 @@ import org.springframework.util.Assert; public class DateOperators { /** - * Take the date referenced by given {@literal fieldReference} in the UTC timezone. + * Take the date referenced by given {@literal fieldReference}. * * @param fieldReference must not be {@literal null}. * @return */ public static DateOperatorFactory dateOf(String fieldReference) { - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new DateOperatorFactory(fieldReference, null); - } - - /** - * Take the date referenced by given {@literal fieldReference} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset. If null, UTC is assumed. - * @since 2.1 - * @return - */ - public static DateOperatorFactory dateOfWithTimezone(String fieldReference, @Nullable String timezone) { - - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new DateOperatorFactory(fieldReference, timezone); - } - - /** - * Take the date referenced by given {@literal fieldReference} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param fieldReference must not be {@literal null}. - * @param timezoneExpression Must not be null. The expression to bind the timezone value from - * @since 2.1 - * @return - */ - public static DateOperatorFactory dateOfWithTimezoneOf(String fieldReference, - AggregationExpression timezoneExpression) { - - Assert.hasText(fieldReference, "FieldReference must not be null!"); - Assert.notNull(timezoneExpression, "timezoneExpression must not be null!"); - return new DateOperatorFactory(fieldReference, timezoneExpression); - } - - /** - * Take the date referenced by given {@literal fieldReference} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param fieldReference must not be {@literal null}. The field to bind the date value from - * @param timezoneField Must not be null. The name of the field to bind the timezone value from - * @since 2.1 - * @return - */ - public static DateOperatorFactory dateOfWithTimezoneOf(String fieldReference, String timezoneField) { - - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new DateOperatorFactory(fieldReference, Fields.field(timezoneField)); + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new DateOperatorFactory(fieldReference); } /** - * Take the date resulting from the given {@link AggregationExpression} in the UTC timezone. + * Take the date resulting from the given {@link AggregationExpression}. * * @param expression must not be {@literal null}. * @return */ public static DateOperatorFactory dateOf(AggregationExpression expression) { - return DateOperators.dateOfWithTimezone(expression, null); - } - - /** - * Take the date resulting from the given {@link AggregationExpression} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset. If null, UTC is assumed. - * @since 2.1 - * @return - */ - public static DateOperatorFactory dateOfWithTimezone(AggregationExpression expression, @Nullable String timezone) { Assert.notNull(expression, "Expression must not be null!"); - return new DateOperatorFactory(expression, timezone); - } - - /** - * Take the date referenced by given {@link AggregationExpression} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param expression must not be {@literal null}. - * @param timezoneExpression Must not be null. The expression to bind the timezone value from - * @since 2.1 - * @return - */ - public static DateOperatorFactory dateOfWithTimezoneOf(AggregationExpression expression, - AggregationExpression timezoneExpression) { - - Assert.notNull(expression, "expression must not be null!"); - Assert.notNull(timezoneExpression, "timezoneExpression must not be null!"); - return new DateOperatorFactory(expression, timezoneExpression); + return new DateOperatorFactory(expression); } /** - * Take the date referenced by given {@link AggregationExpression} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Take the given value as date. + *

+ * This can be one of: + *

    + *
  • {@link java.util.Date}
  • + *
  • {@link java.util.Calendar}
  • + *
  • {@link java.time.Instant}
  • + *
  • {@link java.time.ZonedDateTime}
  • + *
  • {@link java.lang.Long}
  • + *
  • {@link Field}
  • + *
  • {@link AggregationExpression}
  • + *
* - * @param expression must not be {@literal null}. - * @param timezoneField Must not be null. The name of the field to bind the timezone value from + * @param value must not be {@literal null}. + * @return new instance of {@link DateOperatorFactory}. * @since 2.1 - * @return */ - public static DateOperatorFactory dateOfWithTimezoneOf(AggregationExpression expression, String timezoneField) { + public static DateOperatorFactory dateValue(Object value) { - Assert.notNull(expression, "expression must not be null!"); - return new DateOperatorFactory(expression, Fields.field(timezoneField)); + Assert.notNull(value, "Value must not be null!"); + return new DateOperatorFactory(value); } /** - * Take the current date as supplied by the {@link DateFactory} in the UTC timezone + * Construct a Date object by providing the date’s constituent properties.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param factory not nullable. The DateFactory to get the current date from + * @return new instance of {@link DateFromPartsOperatorFactory}. * @since 2.1 - * @return */ - public static DateOperatorFactory dateOf(DateFactory factory) { - return new DateOperatorFactory(factory, null); + public static DateFromPartsOperatorFactory dateFromParts() { + return new DateFromPartsOperatorFactory(Timezone.none()); } /** - * Take the current date resulting from the given {@link DateFactory} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. If using Mongo prior to 3.6, - * specify timezone as null. + * Construct a Date object from the given date {@link String}.
+ * To use a {@link Field field reference} or {@link AggregationExpression} as source of the date string consider + * {@link DateOperatorFactory#fromString()} or {@link DateFromString#fromStringOf(AggregationExpression)}.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param factory not nullable. The DateFactory to get the current date from - * @param timezone nullable. The timezone ID or offset. If null, UTC is assumed. Must specify as null if using Mongo - * prior to 3.6 + * @return new instance of {@link DateFromPartsOperatorFactory}. * @since 2.1 - * @return */ - public static DateOperatorFactory dateOfWithTimezone(DateFactory factory, @Nullable String timezone) { - return new DateOperatorFactory(factory, timezone); + public static DateFromString dateFromString(String value) { + return DateFromString.fromString(value); } /** - * Take the date referenced by given {@link DateFactory} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Timezone represents a MongoDB timezone abstraction which can be represented with a timezone ID or offset as a + * {@link String}. Also accepts a {@link AggregationExpression} or {@link Field} that resolves to a {@link String} of + * either Olson Timezone Identifier or a UTC Offset.
+ * + * + * + * + * + * + * + * + * + * + * + * + * + *
FormatExample
Olson Timezone Identifier"America/New_York"
+ * "Europe/London"
+ * "GMT"
UTC Offset+/-[hh]:[mm], e.g. "+04:45"
+ * -[hh][mm], e.g. "-0530"
+ * +/-[hh], e.g. "+03"
+ * NOTE: Support for timezones in aggregations Requires MongoDB 3.6 or later. * - * @param factory not nullable. The DateFactory to get the current date from - * @param timezoneExpression Must not be null. The expression to bind the timezone value from + * @author Christoph Strobl * @since 2.1 - * @return */ - public static DateOperatorFactory dateOfWithTimezoneOf(DateFactory factory, - AggregationExpression timezoneExpression) { - - Assert.notNull(factory, "Factory must not be null!"); - Assert.notNull(timezoneExpression, "timezoneExpression must not be null!"); - return new DateOperatorFactory(factory, timezoneExpression); - } + public static class Timezone { - /** - * Take the date referenced by given {@link DateFactory}in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param factory {@link DateFactory#LOCAL_DATE_FACTORY}. Defaults to {@link DateOperators#getCurrentDateFactory} if - * null. - * @param timezoneField Must not be null. The name of the field to bind the timezone value from - * @since 2.1 - * @return - */ - public static DateOperatorFactory dateOfWithTimezoneOf(DateFactory factory, String timezoneField) { + private static final Timezone NONE = new Timezone(null); - Assert.notNull(factory, "Factory must not be null!"); - Assert.notNull(timezoneField, "timezoneField must not be null!"); - return new DateOperatorFactory(factory, Fields.field(timezoneField)); - } + private final @Nullable Object value; - /** - * Take the current date using the default {@link DateOperators#getCurrentDateFactory()} in the UTC timezone. - * - * @since 2.1 - * @return - */ - public static DateOperatorFactory currentDate() { - return dateOf(CURRENT_DATE_FACTORY); - } + private Timezone(@Nullable Object value) { + this.value = value; + } - /** - * Take the current date using the default {@link DateOperators#getCurrentDateFactory()} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param timezone nullable. The timezone ID or offset. If null, UTC is assumed. - * @since 2.1 - * @return - */ - public static DateOperatorFactory currentDateWithTimezone(@Nullable String timezone) { - return DateOperators.dateOfWithTimezone(CURRENT_DATE_FACTORY, timezone); - } + /** + * Return an empty {@link Timezone}. + * + * @return never {@literal null}. + */ + public static Timezone none() { + return NONE; + } - /** - * Take the current date using the default {@link DateOperators#getCurrentDateFactory()} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param timezoneExpression Must not be null. The name of the field to bind the timezone value from - * @since 2.1 - * @return - */ - public static DateOperatorFactory currentDateWithTimezoneOf(AggregationExpression timezoneExpression) { - return DateOperators.dateOfWithTimezoneOf(CURRENT_DATE_FACTORY, timezoneExpression); - } + /** + * Create a {@link Timezone} for the given value which must be a valid expression that resolves to a {@link String} + * representing an Olson Timezone Identifier or UTC Offset. + * + * @param value the plain timezone {@link String}, a {@link Field} holding the timezone or an + * {@link AggregationExpression} resulting in the timezone. + * @return new instance of {@link Timezone}. + */ + public static Timezone valueOf(Object value) { - /** - * Take the current date using the default {@link DateOperators#getCurrentDateFactory()} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param timezoneField Must not be null. The name of the field to bind the timezone value from - * @since 2.1 - * @return - */ - public static DateOperatorFactory currentDateWithTimezoneOf(String timezoneField) { - return DateOperators.dateOfWithTimezoneOf(CURRENT_DATE_FACTORY, timezoneField); - } + Assert.notNull(value, "Value must not be null!"); + return new Timezone(value); + } - /** - * @see DateFromParts#fromParts - * @since 2.1 - * @author Matt Morrissette - * @return - */ - public static DateFromParts.CalendarDatePartsBuilder dateFromParts() { - return DateFromParts.fromParts(); - } + /** + * Create a {@link Timezone} for the {@link Field} reference holding the Olson Timezone Identifier or UTC Offset. + * + * @param fieldReference the {@link Field} holding the timezone. + * @return new instance of {@link Timezone}. + */ + public static Timezone ofField(String fieldReference) { + return valueOf(Fields.field(fieldReference)); + } - /** - * @see DateFromParts#fromIsoWeekParts - * @since 2.1 - * @author Matt Morrissette - * @return - */ - public static DateFromParts.IsoWeekDatePartsBuilder dateFromIsoWeekParts() { - return DateFromParts.fromIsoWeekParts(); + /** + * Create a {@link Timezone} for the {@link AggregationExpression} resulting in the Olson Timezone Identifier or UTC + * Offset. + * + * @param value the {@link AggregationExpression} resulting in the timezone. + * @return new instance of {@link Timezone}. + */ + public static Timezone ofExpression(AggregationExpression expression) { + return valueOf(expression); + } } /** @@ -301,757 +192,483 @@ public class DateOperators { */ public static class DateOperatorFactory { - private final String fieldReference; - private final AggregationExpression expression; - private final DateFactory dateFactory; - private final Object timezone; + private final @Nullable String fieldReference; + private final @Nullable Object dateValue; + private final @Nullable AggregationExpression expression; + private final Timezone timezone; /** - * Creates new {@link ArithmeticOperatorFactory} for given {@literal fieldReference} in the UTC timezone. - * - * @param fieldReference must not be {@literal null}. + * @param fieldReference + * @param expression + * @param value + * @param timezone + * @since 2.1 */ - public DateOperatorFactory(String fieldReference) { - this(fieldReference, null); - } + private DateOperatorFactory(@Nullable String fieldReference, @Nullable AggregationExpression expression, + @Nullable Object value, Timezone timezone) { - private DateOperatorFactory(String fieldReference, Object timezone) { - Assert.hasText(fieldReference, "FieldReference must not be null!"); this.fieldReference = fieldReference; - this.expression = null; - this.dateFactory = null; + this.expression = expression; + this.dateValue = value; this.timezone = timezone; } /** - * Creates new {@link ArithmeticOperatorFactory} for given {@link AggregationExpression} in the UTC timezone. + * Creates new {@link DateOperatorFactory} for given {@literal fieldReference}. * - * @param expression must not be {@literal null}. + * @param fieldReference must not be {@literal null}. */ - public DateOperatorFactory(AggregationExpression expression) { - this(expression, null); - } - - private DateOperatorFactory(AggregationExpression expression, Object timezone) { - - Assert.notNull(expression, "Expression must not be null!"); - this.fieldReference = null; - this.expression = expression; - this.timezone = timezone; - this.dateFactory = null; - } - - private DateOperatorFactory(@Nullable DateFactory dateFactory, Object timezone) { - - this.fieldReference = null; - this.expression = null; - this.timezone = timezone; - this.dateFactory = dateFactory != null ? dateFactory : CURRENT_DATE_FACTORY; - } + public DateOperatorFactory(String fieldReference) { - private DateOperatorFactory(String fieldRefererence, AggregationExpression expression, DateFactory dateFactory, - Object timezone) { + this(fieldReference, null, null, Timezone.none()); - this.fieldReference = fieldRefererence; - this.expression = expression; - this.dateFactory = dateFactory; - this.timezone = timezone; + Assert.notNull(fieldReference, "FieldReference must not be null!"); } /** - * @param timezone nullable. The timezone ID or offset as a String. - * @return a new DateOperator factory with the same date reference/expression/factory but with the given timezone + * Creates new {@link DateOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. */ - public DateOperatorFactory withTimezone(String timezone) { - return new DateOperatorFactory(fieldReference, expression, dateFactory, timezone); - } + public DateOperatorFactory(AggregationExpression expression) { - /** - * @param timezoneField not nullable. The field reference to bind the timezone from. - * @return a new DateOperator factory with the same date reference/expression/factory but with the given timezone - */ - public DateOperatorFactory withTimezoneOf(String timezoneField) { + this(null, expression, null, Timezone.none()); - Assert.hasText(timezoneField, "timezoneField cannot be null or empty"); - return new DateOperatorFactory(fieldReference, expression, dateFactory, Fields.field(timezoneField)); + Assert.notNull(expression, "Expression must not be null!"); } /** - * @param timezoneExpression not nullable. The expression to bind the timezone from - * @return a new DateOperator factory with the same date reference/expression/factory but with the given timezone + * Creates new {@link DateOperatorFactory} for given {@code value} that resolves to a Date. + *

+ *

    + *
  • {@link java.util.Date}
  • + *
  • {@link java.util.Calendar}
  • + *
  • {@link java.time.Instant}
  • + *
  • {@link java.time.ZonedDateTime}
  • + *
  • {@link java.lang.Long}
  • + *
+ * + * @param value must not be {@literal null}. + * @since 2.1 */ - public DateOperatorFactory withTimezoneOf(AggregationExpression timezoneExpression) { + public DateOperatorFactory(Object value) { - Assert.notNull(timezoneExpression, "timezoneExpression cannot be null or empty"); - return new DateOperatorFactory(fieldReference, expression, dateFactory, timezoneExpression); - } + this(null, null, value, Timezone.none()); - /** - * Creates new {@link AggregationExpression} that returns the day of the year for a date as a number between 1 and - * 366 in the factory timezone (default UTC). - * - * @return - */ - public DayOfYear dayOfYear() { - return dayOfYear(timezone); + Assert.notNull(value, "Value must not be null!"); } /** - * Creates new {@link AggregationExpression} that returns the day of the year for a date as a number between 1 and - * 366 in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Create a new {@link DateOperatorFactory} bound to a given {@link Timezone}.
+ * NOTE: Requires Mongo 3.6 or later. * - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. + * @param timezone must not be {@literal null}. Use {@link Timezone#none()} instead. + * @return new instance of {@link DateOperatorFactory}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. * @since 2.1 - * @return */ - public DayOfYear dayOfYear(@Nullable Object timezone) { - return usesFieldRef() ? DayOfYear.dayOfYear(fieldReference, timezone) - : usesExpression() ? DayOfYear.dayOfYear(expression, timezone) : DayOfYear.dayOfYear(dateFactory, timezone); + public DateOperatorFactory withTimezone(Timezone timezone) { + + Assert.notNull(timezone, "Timezone must not be null!"); + return new DateOperatorFactory(fieldReference, expression, dateValue, timezone); } /** - * Creates new {@link AggregationExpression} that returns the day of the month for a date as a number between 1 and - * 31 in the factory timezone (default UTC). + * Creates new {@link AggregationExpression} that returns the day of the year for a date as a number between 1 and + * 366. * * @return */ - public DayOfMonth dayOfMonth() { - return dayOfMonth(timezone); + public DayOfYear dayOfYear() { + return applyTimezone(DayOfYear.dayOfYear(dateReference()), timezone); } /** * Creates new {@link AggregationExpression} that returns the day of the month for a date as a number between 1 and - * 31 in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * 31. * - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. - * @since 2.1 * @return */ - public DayOfMonth dayOfMonth(@Nullable Object timezone) { - return usesFieldRef() ? DayOfMonth.dayOfMonth(fieldReference, timezone) - : usesExpression() ? DayOfMonth.dayOfMonth(expression, timezone) - : DayOfMonth.dayOfMonth(dateFactory, timezone); + public DayOfMonth dayOfMonth() { + return applyTimezone(DayOfMonth.dayOfMonth(dateReference()), timezone); } /** * Creates new {@link AggregationExpression} that returns the day of the week for a date as a number between 1 - * (Sunday) and 7 (Saturday) in the factory timezone (default UTC). + * (Sunday) and 7 (Saturday). * * @return */ public DayOfWeek dayOfWeek() { - return dayOfWeek(timezone); + return applyTimezone(DayOfWeek.dayOfWeek(dateReference()), timezone); } /** - * Creates new {@link AggregationExpression} that returns the day of the week for a date as a number between 1 - * (Sunday) and 7 (Saturday) in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link AggregationExpression} that returns the year portion of a date. * - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. - * @since 2.1 * @return */ - public DayOfWeek dayOfWeek(@Nullable Object timezone) { - return usesFieldRef() ? DayOfWeek.dayOfWeek(fieldReference, timezone) - : usesExpression() ? DayOfWeek.dayOfWeek(expression, timezone) : DayOfWeek.dayOfWeek(dateFactory, timezone); + public Year year() { + return applyTimezone(Year.year(dateReference()), timezone); } /** - * Creates new {@link AggregationExpression} that returns the year portion of a date in the factory timezone - * (default UTC). + * Creates new {@link AggregationExpression} that returns the month of a date as a number between 1 and 12. * * @return */ - public Year year() { - return year(timezone); + public Month month() { + return applyTimezone(Month.month(dateReference()), timezone); } /** - * Creates new {@link AggregationExpression} that returns the year portion of a date in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link AggregationExpression} that returns the week of the year for a date as a number between 0 and + * 53. * - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. - * @since 2.1 * @return */ - public Year year(@Nullable Object timezone) { - return usesFieldRef() ? Year.yearOf(fieldReference, timezone) - : usesExpression() ? Year.yearOf(expression, timezone) : Year.yearOf(dateFactory, timezone); + public Week week() { + return applyTimezone(Week.week(dateReference()), timezone); } /** - * Creates new {@link AggregationExpression} that returns the quarter of a date as a number between 1 and 4 in the - * factory timezone (default UTC). + * Creates new {@link AggregationExpression} that returns the hour portion of a date as a number between 0 and 23. * * @return */ - public Quarter quarter() { - return quarter(timezone); + public Hour hour() { + return applyTimezone(Hour.hour(dateReference()), timezone); } /** - * Creates new {@link AggregationExpression} that returns the business quarter of a date as a number between 1 and 4 - * in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link AggregationExpression} that returns the minute portion of a date as a number between 0 and 59. * - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. - * @since 2.1 * @return */ - public Quarter quarter(@Nullable Object timezone) { - return usesFieldRef() ? Quarter.quarterOf(fieldReference, timezone) - : usesExpression() ? Quarter.quarterOf(expression, timezone) : Quarter.quarterOf(dateFactory, timezone); + public Minute minute() { + return applyTimezone(Minute.minute(dateReference()), timezone); } /** - * Creates new {@link AggregationExpression} that returns the month of a date as a number between 1 and 12 in the - * factory timezone (default UTC). + * Creates new {@link AggregationExpression} that returns the second portion of a date as a number between 0 and 59, + * but can be 60 to account for leap seconds. * * @return */ - public Month month() { - return month(timezone); + public Second second() { + return applyTimezone(Second.second(dateReference()), timezone); } /** - * Creates new {@link AggregationExpression} that returns the month of a date as a number between 1 and 12 in the - * given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link AggregationExpression} that returns the millisecond portion of a date as an integer between 0 + * and 999. * - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. - * @since 2.1 * @return */ - public Month month(@Nullable Object timezone) { - return usesFieldRef() ? Month.monthOf(fieldReference, timezone) - : usesExpression() ? Month.monthOf(expression, timezone) : Month.monthOf(dateFactory, timezone); + public Millisecond millisecond() { + return applyTimezone(Millisecond.millisecond(dateReference()), timezone); } /** - * Creates new {@link AggregationExpression} that returns the week of the year for a date as a number between 0 and - * 53 in the factory timezone (default UTC). + * Creates new {@link AggregationExpression} that converts a date object to a string according to a user-specified + * {@literal format}. * + * @param format must not be {@literal null}. * @return */ - public Week week() { - return week(timezone); + public DateToString toString(String format) { + return applyTimezone(DateToString.dateToString(dateReference()).toString(format), timezone); } /** - * Creates new {@link AggregationExpression} that returns the week of the year for a date as a number between 0 and - * 53 in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link AggregationExpression} that returns the weekday number in ISO 8601-2018 format, ranging from 1 + * (for Monday) to 7 (for Sunday). * - * @param timezone nullable. The timezone ID or offset. If null, UTC is assumed. - * @since 2.1 * @return */ - public Week week(@Nullable Object timezone) { - return usesFieldRef() ? Week.weekOf(fieldReference, timezone) - : usesExpression() ? Week.weekOf(expression, timezone) : Week.weekOf(dateFactory, timezone); + public IsoDayOfWeek isoDayOfWeek() { + return applyTimezone(IsoDayOfWeek.isoDayWeek(dateReference()), timezone); } /** - * Creates new {@link AggregationExpression} that returns the hour portion of a date as a number between 0 and 23 in - * the factory timezone (default UTC). + * Creates new {@link AggregationExpression} that returns the week number in ISO 8601-2018 format, ranging from 1 to + * 53. * * @return */ - public Hour hour() { - return hour(timezone); + public IsoWeek isoWeek() { + return applyTimezone(IsoWeek.isoWeek(dateReference()), timezone); } /** - * Creates new {@link AggregationExpression} that returns the hour portion of a date as a number between 0 and 23 in - * the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link AggregationExpression} that returns the year number in ISO 8601-2018 format. * - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. - * @since 2.1 * @return */ - public Hour hour(@Nullable Object timezone) { - return usesFieldRef() ? Hour.hourOf(fieldReference, timezone) - : usesExpression() ? Hour.hourOf(expression, timezone) : Hour.hourOf(dateFactory, timezone); + public IsoWeekYear isoWeekYear() { + return applyTimezone(IsoWeekYear.isoWeekYear(dateReference()), timezone); } /** - * Creates new {@link AggregationExpression} that returns the minute portion of a date as a number between 0 and 59 - * in the factory timezone (default UTC). + * Creates new {@link AggregationExpression} that returns a document containing the constituent parts of the date as + * individual properties.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @return + * @return new instance of {@link DateToParts}. + * @since 2.1 */ - public Minute minute() { - return minute(timezone); + public DateToParts toParts() { + return applyTimezone(DateToParts.dateToParts(dateReference()), timezone); } /** - * Creates new {@link AggregationExpression} that returns the minute portion of a date as a number between 0 and 59 - * in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link AggregationExpression} that converts a date/time string to a date object.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. + * @return new instance of {@link DateFromString}. * @since 2.1 - * @return */ - public Minute minute(@Nullable Object timezone) { - return usesFieldRef() ? Minute.minuteOf(fieldReference, timezone) - : usesExpression() ? Minute.minuteOf(expression, timezone) : Minute.minuteOf(dateFactory, timezone); + public DateFromString fromString() { + return applyTimezone(DateFromString.fromString(dateReference()), timezone); } - /** - * Creates new {@link AggregationExpression} that returns the second portion of a date as a number between 0 and 59, - * but can be 60 to account for leap seconds in the factory timezone (default UTC). - * - * @return - */ - public Second second() { - return second(timezone); + private Object dateReference() { + + if (usesFieldRef()) { + return Fields.field(fieldReference); + } + + return usesExpression() ? expression : dateValue; } - /** - * Creates new {@link AggregationExpression} that returns the second portion of a date as a number between 0 and 59, - * but can be 60 to account for leap seconds in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. - * @since 2.1 - * @return - */ - public Second second(@Nullable Object timezone) { - return usesFieldRef() ? Second.secondOf(fieldReference, timezone) - : usesExpression() ? Second.secondOf(expression, timezone) : Second.secondOf(dateFactory, timezone); + private boolean usesFieldRef() { + return fieldReference != null; } - /** - * Creates new {@link AggregationExpression} that returns the millisecond portion of a date as an integer between 0 - * and 999 in the factory timezone (default UTC). - * - * @return - */ - public Millisecond millisecond() { - return millisecond(timezone); + private boolean usesExpression() { + return expression != null; + } + } + + /** + * @author Matt Morrissette + * @author Christoph Strobl + * @since 2.1 + */ + public static class DateFromPartsOperatorFactory { + + private final Timezone timezone; + + private DateFromPartsOperatorFactory(Timezone timezone) { + this.timezone = timezone; } /** - * Creates new {@link AggregationExpression} that returns the millisecond portion of a date as an integer between 0 - * and 999 in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Set the {@literal week date year} to the given value which must resolve to a weekday in range {@code 0 - 9999}. + * Can be a simple value, {@link Field field reference} or {@link AggregationExpression expression}. * - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. - * @since 2.1 - * @return + * @param isoWeekYear must not be {@literal null}. + * @return new instance of {@link IsoDateFromParts} with {@link Timezone} if set. + * @throws IllegalArgumentException if given {@literal isoWeekYear} is {@literal null}. */ - public Millisecond millisecond(@Nullable Object timezone) { - return usesFieldRef() ? Millisecond.millisecondOf(fieldReference, timezone) - : usesExpression() ? Millisecond.millisecondOf(expression, timezone) - : Millisecond.millisecondOf(dateFactory, timezone); + public IsoDateFromParts isoWeekYear(Object isoWeekYear) { + return applyTimezone(IsoDateFromParts.dateFromParts().isoWeekYear(isoWeekYear), timezone); } /** - * Creates new {@link AggregationExpression} that returns the weekday number in ISO 8601-2018 format, ranging from 1 - * (for Monday) to 7 (for Sunday) in the factory timezone (default UTC). + * Set the {@literal week date year} to the value resolved by following the given {@link Field field reference}. * - * @return + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link IsoDateFromParts} with {@link Timezone} if set. + * @throws IllegalArgumentException if given {@literal fieldReference} is {@literal null}. */ - public IsoDayOfWeek isoDayOfWeek() { - return isoDayOfWeek(timezone); + public IsoDateFromParts isoWeekYearOf(String fieldReference) { + return isoWeekYear(Fields.field(fieldReference)); } /** - * Creates new {@link AggregationExpression} that returns the weekday number in ISO 8601-2018 format, ranging from 1 - * (for Monday) to 7 (for Sunday) in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Set the {@literal week date year} to the result of the given {@link AggregationExpression expression}. * - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. - * @since 2.1 - * @return + * @param expression must not be {@literal null}. + * @return new instance of {@link IsoDateFromParts} with {@link Timezone} if set. + * @throws IllegalArgumentException if given {@literal expression} is {@literal null}. */ - public IsoDayOfWeek isoDayOfWeek(@Nullable Object timezone) { - return usesFieldRef() ? IsoDayOfWeek.isoDayOfWeek(fieldReference, timezone) - : usesExpression() ? IsoDayOfWeek.isoDayOfWeek(expression, timezone) - : IsoDayOfWeek.isoDayOfWeek(dateFactory, timezone); + public IsoDateFromParts isoWeekYearOf(AggregationExpression expression) { + return isoWeekYear(expression); } /** - * Creates new {@link AggregationExpression} that returns the week number in ISO 8601-2018 format, ranging from 1 to - * 53 in the factory timezone (default UTC). + * Set the {@literal year} to the given value which must resolve to a calendar year. Can be a simple value, + * {@link Field field reference} or {@link AggregationExpression expression}. * - * @return + * @param year must not be {@literal null}. + * @return new instance of {@link DateFromParts} with {@link Timezone} if set. + * @throws IllegalArgumentException if given {@literal year} is {@literal null} */ - public IsoWeek isoWeek() { - return isoWeek(timezone); + public DateFromParts year(Object year) { + return applyTimezone(DateFromParts.dateFromParts().year(year), timezone); } /** - * Creates new {@link AggregationExpression} that returns the week number in ISO 8601-2018 format, ranging from 1 to - * 53 in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Set the {@literal year} to the value resolved by following the given {@link Field field reference}. * - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. - * @since 2.1 - * @return + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link DateFromParts} with {@link Timezone} if set. + * @throws IllegalArgumentException if given {@literal fieldReference} is {@literal null}. */ - public IsoWeek isoWeek(@Nullable Object timezone) { - return usesFieldRef() ? IsoWeek.isoWeekOf(fieldReference, timezone) - : usesExpression() ? IsoWeek.isoWeekOf(expression, timezone) : IsoWeek.isoWeekOf(dateFactory, timezone); + public DateFromParts yearOf(String fieldReference) { + return year(Fields.field(fieldReference)); } /** - * Creates new {@link AggregationExpression} that returns the year number in ISO 8601-2018 format in the factory - * timezone (default UTC). + * Set the {@literal year} to the result of the given {@link AggregationExpression expression}. * - * @return + * @param expression must not be {@literal null}. + * @return new instance of {@link DateFromParts} with {@link Timezone} if set. + * @throws IllegalArgumentException if given {@literal expression} is {@literal null}. */ - public IsoWeekYear isoWeekYear() { - return isoWeekYear(timezone); + public DateFromParts yearOf(AggregationExpression expression) { + return year(expression); } /** - * Creates new {@link AggregationExpression} that returns the year number in ISO 8601-2018 format in the given - * timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Create a new {@link DateFromPartsOperatorFactory} bound to a given {@link Timezone}.
* - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. - * @since 2.1 - * @return + * @param timezone must not be {@literal null}. Use {@link Timezone#none()} instead. + * @return new instance of {@link DateFromPartsOperatorFactory}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. */ - public IsoWeekYear isoWeekYear(@Nullable Object timezone) { - return usesFieldRef() ? IsoWeekYear.isoWeekYearOf(fieldReference, timezone) - : usesExpression() ? IsoWeekYear.isoWeekYearOf(expression, timezone) - : IsoWeekYear.isoWeekYearOf(dateFactory, timezone); + public DateFromPartsOperatorFactory withTimezone(Timezone timezone) { + + Assert.notNull(timezone, "Timezone must not be null!"); + return new DateFromPartsOperatorFactory(timezone); } + } - /** - * Creates new {@link AggregationExpression} that converts a date object to a string according to a user-specified - * {@literal format} in the factory timezone (default UTC). - * - * @param format must not be {@literal null}. - * @since 2.1 - * @return - */ - public DateToString toString(String format) { - return toString(format, timezone); - } - - /** - * Creates new {@link AggregationExpression} that converts a date object to a string according to a user-specified - * {@literal format} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param format must not be {@literal null}. - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. - * @since 2.1 - * @return - */ - public DateToString toString(String format, @Nullable Object timezone) { - return (usesFieldRef() ? DateToString.dateOf(fieldReference, timezone) - : usesExpression() ? DateToString.dateOf(expression, timezone) : DateToString.dateOf(dateFactory, timezone)) - .toString(format); - } - - /** - * Creates new {@link AggregationExpression} that converts a string to a date object in the factory timezone - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @since 2.1 - * @return - */ - public DateFromString fromString() { - return fromString(timezone); - } + /** + * {@link AggregationExpression} capable of setting a given {@link Timezone}. + * + * @author Christoph Strobl + * @since 2.1 + */ + public static abstract class TimezonedDateAggregationExpression extends AbstractAggregationExpression { - /** - * Creates new {@link AggregationExpression} that converts a string to a date object in the given timezone - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. - * @since 2.1 - * @return - */ - public DateFromString fromString(@Nullable Object timezone) { - return usesFieldRef() ? DateFromString.dateFromString(fieldReference, timezone) - : usesExpression() ? DateFromString.dateFromString(expression, timezone) - : DateFromString.dateFromString(dateFactory, timezone); + protected TimezonedDateAggregationExpression(Object value) { + super(value); } /** - * Creates new {@link AggregationExpression} that converts a string to a date object in the factory timezone using - * calendar parts (year/month/day) - *

- * WARNING: Mongo 3.6+ only + * Append the {@code timezone} to a given source. The source itself can be a {@link Map} of already set properties + * or a single value. In case of single value {@code source} the value will be added as {@code date} property. * - * @since 2.1 + * @param source must not be {@literal null}. + * @param timezone must not be {@literal null} use {@link Timezone#none()} instead. * @return */ - public DateToParts toParts() { - return toParts(timezone, null); - } + protected static java.util.Map appendTimezone(Object source, Timezone timezone) { - /** - * Creates new {@link AggregationExpression} that converts a string to a date object in the factory timezone using - * isoWeek parts (isoWeekYear/isoWeek/isoDayOfWeek) - *

- * WARNING: Mongo 3.6+ only - * - * @since 2.1 - * @return - */ - public DateToParts toIsoWeekParts() { - return toParts(timezone, true); - } + java.util.Map args; - /** - * Creates new {@link AggregationExpression} that converts a string to a date object in the factory timezone - *

- * WARNING: Mongo 3.6+ only - * - * @param iso8601 If set to true, modifies the output document to use ISO week date fields. Defaults to false. - * @since 2.1 - * @return - */ - public DateToParts toParts(Boolean iso8601) { - return toParts(timezone, iso8601); - } + if (source instanceof Map) { + args = new LinkedHashMap<>((Map) source); + } else { + args = new LinkedHashMap<>(2); + args.put("date", source); + } - /** - * Creates new {@link AggregationExpression} that converts a string to a date object in the given timezone using - * calendar parts (year/month/day) - *

- * WARNING: Mongo 3.6+ only - * - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. - * @since 2.1 - * @return - */ - public DateToParts toParts(@Nullable Object timezone) { - return toParts(timezone, null); - } + if (!ObjectUtils.nullSafeEquals(Timezone.none(), timezone)) { + args.put("timezone", timezone.value); + } else if (args.containsKey("timezone")) { + args.remove("timezone"); + } - /** - * Creates new {@link AggregationExpression} that converts a string to a date object in the given timezone using - * isoWeek parts (isoWeekYear/isoWeek/isoDayOfWeek) - *

- * WARNING: Mongo 3.6+ only - * - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. - * @since 2.1 - * @return - */ - public DateToParts toIsoWeekParts(@Nullable Object timezone) { - return toParts(timezone, true); + return args; } /** - * Creates new {@link AggregationExpression} that converts a string to a date object in the given timezone - *

- * WARNING: Mongo 3.6+ only + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param timezone nullable. Overrides factory timezone. The timezone ID or offset as a String. Also accepts a - * {@link AggregationExpression} or {@link Field}. If null UTC is assumed. - * @param iso8601 If set to true, modifies the output document to use ISO week date fields - * (isoWeekYear/isoWeek/isoDayOfWeek). Defaults to false. - * @since 2.1 - * @return + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. */ - public DateToParts toParts(@Nullable Object timezone, @Nullable Boolean iso8601) { - return usesFieldRef() ? DateToParts.dateToParts(fieldReference, timezone, iso8601) - : usesExpression() ? DateToParts.dateToParts(expression, timezone, iso8601) - : DateToParts.dateToParts(dateFactory, timezone, iso8601); - } - - private boolean usesFieldRef() { - return fieldReference != null; - } - - private boolean usesExpression() { - return expression != null; - } - } - - // contemplates support for the future functionality described in: - // https://jira.mongodb.org/browse/SERVER-23656 - private static DateFactory CURRENT_DATE_FACTORY = DateFactory.LOCAL_DATE_FACTORY; - - /** - * Sets the {@link DateFactory} used by {@link DateOperators#currentDate()} and - * {@link DateOperators#currentDate(java.lang.String)} for the entire application (statically). - * - * @param defaultFactory - */ - public static void setCurrentDateFactory(final DateFactory defaultFactory) { - - Assert.notNull(defaultFactory, "Default DateFactory cannot be null"); - CURRENT_DATE_FACTORY = defaultFactory; - } - - /** - * @return the {@link DateFactory} used by {@link DateOperators#currentDate()} and - * {@link DateOperators#currentDate(java.lang.String)} - */ - public static DateFactory getCurrentDateFactory() { - return CURRENT_DATE_FACTORY; - } - - /** - * New in Mongo 3.6 is support for timezone specification with all date types. - *

- * WARNING: Using timezone requires Mongo 3.6+ and will error on prior versions of Mongo - * - * @since 2.1 - */ - private abstract static class DateAggregationExpression extends AbstractAggregationExpression { - - protected DateAggregationExpression(Object date, @Nullable Object timezone) { - super(arguments(date, timezone)); - } - - private static Object arguments(Object date, @Nullable Object timezone) { - - if (timezone != null) { - Assert.isTrue(DateAggregationExpression.isValidTimezoneObject(timezone), - () -> "Timezone was not a valid timezone: " + timezone - + ". Must be String, AggregationExpression or Field"); - - java.util.Map args = new LinkedHashMap<>(4); - args.put("date", date); - args.put("timezone", timezone); - return args; - } else { - return date; - } - } + protected abstract TimezonedDateAggregationExpression withTimezone(Timezone timezone); - public static boolean isValidTimezoneObject(Object timezone) { - return timezone == null - || (timezone instanceof String || timezone instanceof Field || timezone instanceof AggregationExpression); + protected boolean hasTimezone() { + return contains("timezone"); } - } /** * {@link AggregationExpression} for {@code $dayOfYear}. * * @author Christoph Strobl - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/dayOfYear/ + * @author Matt Morrissette */ - public static class DayOfYear extends DateAggregationExpression { - - private DayOfYear(Object value, Object timezone) { - super(value, timezone); - } + public static class DayOfYear extends TimezonedDateAggregationExpression { - @Override - protected String getMongoMethod() { - return "$dayOfYear"; + private DayOfYear(Object value) { + super(value); } /** - * Creates new {@link DayOfYear} in UTC. + * Creates new {@link DayOfYear}. * - * @param fieldReference must not be {@literal null}. - * @return + * @param value must not be {@literal null} and resolve to field, expression or object that represents a date. + * @return new instance of {@link DayOfYear}. + * @throws IllegalArgumentException if given value is {@literal null}. + * @since 2.1 */ - public static DayOfYear dayOfYear(String fieldReference) { - return dayOfYear(fieldReference, null); + public static DayOfYear dayOfYear(Object value) { + + Assert.notNull(value, "value must not be null!"); + return new DayOfYear(value); } /** - * Creates new {@link DayOfYear} for the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link DayOfYear}. * * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 * @return */ - public static DayOfYear dayOfYear(String fieldReference, @Nullable Object timezone) { + public static DayOfYear dayOfYear(String fieldReference) { - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new DayOfYear(Fields.field(fieldReference), timezone); + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return dayOfYear(Fields.field(fieldReference)); } /** - * Creates new {@link DayOfYear} in UTC. + * Creates new {@link DayOfYear}. * * @param expression must not be {@literal null}. * @return */ public static DayOfYear dayOfYear(AggregationExpression expression) { - return dayOfYear(expression, null); - } - - /** - * Creates new {@link DayOfYear} for the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 - * @return - */ - public static DayOfYear dayOfYear(AggregationExpression expression, @Nullable Object timezone) { Assert.notNull(expression, "Expression must not be null!"); - return new DayOfYear(expression, timezone); + return dayOfYear((Object) expression); } /** - * Creates new {@link DayOfYear} for current date in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param dateFactory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link DayOfYear}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. * @since 2.1 - * @return */ - public static DayOfYear dayOfYear(DateFactory dateFactory, @Nullable Object timezone) { + @Override + public DayOfYear withTimezone(Timezone timezone) { - Assert.notNull(dateFactory, "dateFactory must not be null!"); - return new DayOfYear(dateFactory, timezone); + Assert.notNull(timezone, "Timezone must not be null."); + return new DayOfYear(appendTimezone(values().iterator().next(), timezone)); + } + + @Override + protected String getMongoMethod() { + return "$dayOfYear"; } } @@ -1059,89 +676,71 @@ public class DateOperators { * {@link AggregationExpression} for {@code $dayOfMonth}. * * @author Christoph Strobl - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/dayOfMonth/ + * @author Matt Morrissette */ - public static class DayOfMonth extends DateAggregationExpression { - - private DayOfMonth(Object value, Object timezone) { - super(value, timezone); - } + public static class DayOfMonth extends TimezonedDateAggregationExpression { - @Override - protected String getMongoMethod() { - return "$dayOfMonth"; + private DayOfMonth(Object value) { + super(value); } /** - * Creates new {@link DayOfMonth} in UTC. + * Creates new {@link DayOfMonth}. * - * @param fieldReference must not be {@literal null}. - * @return + * @param value must not be {@literal null} and resolve to field, expression or object that represents a date. + * @return new instance of {@link DayOfMonth}. + * @throws IllegalArgumentException if given value is {@literal null}. + * @since 2.1 */ - public static DayOfMonth dayOfMonth(String fieldReference) { - return dayOfMonth(fieldReference, null); + public static DayOfMonth dayOfMonth(Object value) { + + Assert.notNull(value, "value must not be null!"); + return new DayOfMonth(value); } /** - * Creates new {@link DayOfMonth} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link DayOfMonth}. * * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 * @return */ - public static DayOfMonth dayOfMonth(String fieldReference, @Nullable Object timezone) { + public static DayOfMonth dayOfMonth(String fieldReference) { - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new DayOfMonth(Fields.field(fieldReference), timezone); + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return dayOfMonth(Fields.field(fieldReference)); } /** - * Creates new {@link DayOfMonth} in UTC. + * Creates new {@link DayOfMonth}. * * @param expression must not be {@literal null}. * @return */ public static DayOfMonth dayOfMonth(AggregationExpression expression) { - return dayOfMonth(expression, null); - } - - /** - * Creates new {@link DayOfMonth} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 - * @return - */ - public static DayOfMonth dayOfMonth(AggregationExpression expression, @Nullable Object timezone) { Assert.notNull(expression, "Expression must not be null!"); - return new DayOfMonth(expression, timezone); + return dayOfMonth((Object) expression); } /** - * Creates new {@link DayOfMonth} for current date in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. If Mongo is less than 3.6, - * timezone must be null. + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param dateFactory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. (supports Mongo prior to 3.6 if null) + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link DayOfMonth}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. * @since 2.1 - * @return */ - public static DayOfMonth dayOfMonth(DateFactory dateFactory, @Nullable Object timezone) { + @Override + public DayOfMonth withTimezone(Timezone timezone) { + + Assert.notNull(timezone, "Timezone must not be null."); + return new DayOfMonth(appendTimezone(values().iterator().next(), timezone)); + } - Assert.notNull(dateFactory, "dateFactory must not be null!"); - return new DayOfMonth(dateFactory, timezone); + @Override + protected String getMongoMethod() { + return "$dayOfMonth"; } } @@ -1149,44 +748,38 @@ public class DateOperators { * {@link AggregationExpression} for {@code $dayOfWeek}. * * @author Christoph Strobl - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/dayOfWeek/ + * @author Matt Morrissette */ - public static class DayOfWeek extends DateAggregationExpression { - - private DayOfWeek(Object value, Object timezone) { - super(value, timezone); - } + public static class DayOfWeek extends TimezonedDateAggregationExpression { - @Override - protected String getMongoMethod() { - return "$dayOfWeek"; + private DayOfWeek(Object value) { + super(value); } /** * Creates new {@link DayOfWeek}. * - * @param fieldReference must not be {@literal null}. - * @return + * @param value must not be {@literal null} and resolve to field, expression or object that represents a date. + * @return new instance of {@link DayOfWeek}. + * @throws IllegalArgumentException if given value is {@literal null}. + * @since 2.1 */ - public static DayOfWeek dayOfWeek(String fieldReference) { - return dayOfWeek(fieldReference, null); + public static DayOfWeek dayOfWeek(Object value) { + + Assert.notNull(value, "value must not be null!"); + return new DayOfWeek(value); } /** - * Creates new {@link DayOfWeek} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link DayOfWeek}. * * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 * @return */ - public static DayOfWeek dayOfWeek(String fieldReference, @Nullable Object timezone) { + public static DayOfWeek dayOfWeek(String fieldReference) { - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new DayOfWeek(Fields.field(fieldReference), timezone); + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return dayOfWeek(Fields.field(fieldReference)); } /** @@ -1196,42 +789,30 @@ public class DateOperators { * @return */ public static DayOfWeek dayOfWeek(AggregationExpression expression) { - return dayOfWeek(expression, null); - } - - /** - * Creates new {@link DayOfWeek} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 - * @return - */ - public static DayOfWeek dayOfWeek(AggregationExpression expression, @Nullable Object timezone) { Assert.notNull(expression, "Expression must not be null!"); - return new DayOfWeek(expression, timezone); + return dayOfWeek((Object) expression); } /** - * Creates new {@link DayOfWeek} for current date in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. If Mongo is less than 3.6, - * timezone must be null. + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param dateFactory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. (supports Mongo prior to 3.6 if null) + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link DayOfWeek}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. * @since 2.1 - * @return */ - public static DayOfWeek dayOfWeek(DateFactory dateFactory, @Nullable Object timezone) { + @Override + public DayOfWeek withTimezone(Timezone timezone) { + + Assert.notNull(timezone, "Timezone must not be null."); + return new DayOfWeek(appendTimezone(values().iterator().next(), timezone)); + } - Assert.notNull(dateFactory, "dateFactory must not be null!"); - return new DayOfWeek(dateFactory, timezone); + @Override + protected String getMongoMethod() { + return "$dayOfWeek"; } } @@ -1239,728 +820,607 @@ public class DateOperators { * {@link AggregationExpression} for {@code $year}. * * @author Christoph Strobl - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/year/ + * @author Matt Morrissette */ - public static class Year extends DateAggregationExpression { - - private Year(Object value, Object timezone) { - super(value, timezone); - } + public static class Year extends TimezonedDateAggregationExpression { - @Override - protected String getMongoMethod() { - return "$year"; + private Year(Object value) { + super(value); } /** - * Creates new {@link Year} in the UTC timezone. + * Creates new {@link Year}. * - * @param fieldReference must not be {@literal null}. - * @return + * @param value must not be {@literal null} and resolve to field, expression or object that represents a date. + * @return new instance of {@link Year}. + * @throws IllegalArgumentException if given value is {@literal null}. + * @since 2.1 */ - public static Year yearOf(String fieldReference) { - return yearOf(fieldReference, null); + public static Year year(Object value) { + + Assert.notNull(value, "value must not be null!"); + return new Year(value); } /** - * Creates new {@link Year} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link Year}. * * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 * @return */ - public static Year yearOf(String fieldReference, @Nullable Object timezone) { + public static Year yearOf(String fieldReference) { - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new Year(Fields.field(fieldReference), timezone); + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return year(Fields.field(fieldReference)); } /** - * Creates new {@link Year} in the UTC timezone. + * Creates new {@link Year}. * * @param expression must not be {@literal null}. * @return */ public static Year yearOf(AggregationExpression expression) { - return yearOf(expression, null); - } - - /** - * Creates new {@link Year} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 - * @return - */ - public static Year yearOf(AggregationExpression expression, @Nullable Object timezone) { Assert.notNull(expression, "Expression must not be null!"); - return new Year(expression, timezone); + return year(expression); } /** - * Creates new {@link Year} for current date in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. If Mongo is less than 3.6, - * timezone must be null. + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param dateFactory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. (supports Mongo prior to 3.6 if null) + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link Year}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. * @since 2.1 - * @return */ - public static Year yearOf(DateFactory dateFactory, @Nullable Object timezone) { + @Override + public Year withTimezone(Timezone timezone) { - Assert.notNull(dateFactory, "dateFactory must not be null!"); - return new Year(dateFactory, timezone); + Assert.notNull(timezone, "Timezone must not be null."); + return new Year(appendTimezone(values().iterator().next(), timezone)); + } + + @Override + protected String getMongoMethod() { + return "$year"; } } /** - * Pseudo {@link AggregationExpression} to represent the current business quarter using conditionals. Can be used in a - * $group aggregation state to group results by business quarter + * {@link AggregationExpression} for {@code $month}. * + * @author Christoph Strobl * @author Matt Morrissette */ - public static class Quarter implements AggregationExpression { - - private final Cond cond; + public static class Month extends TimezonedDateAggregationExpression { - private Quarter(Object value, Object timezone) { - - final Month month = new Month(value, timezone); - cond = when(quarterConditional(month, 3)).then(1).otherwiseValueOf(when(quarterConditional(month, 6)).then(2) - .otherwiseValueOf(when(quarterConditional(month, 9)).then(3).otherwise(4))); - } - - private static AggregationExpression quarterConditional(final Month month, int mininumMonth) { - return ComparisonOperators.valueOf(month).lessThanEqualToValue(mininumMonth); - } - - /** - * Creates new {@link Quarter} in the UTC timezone. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Quarter quarterOf(String fieldReference) { - return quarterOf(fieldReference, null); + private Month(Object value) { + super(value); } /** - * Creates new {@link Quarter} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link Month}. * - * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. + * @param value must not be {@literal null} and resolve to field, expression or object that represents a date. + * @return new instance of {@link Month}. + * @throws IllegalArgumentException if given value is {@literal null}. * @since 2.1 - * @return */ - public static Quarter quarterOf(String fieldReference, @Nullable Object timezone) { + public static Month month(Object value) { - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new Quarter(Fields.field(fieldReference), timezone); + Assert.notNull(value, "value must not be null!"); + return new Month(value); } /** - * Creates new {@link Quarter} in the UTC timezone. + * Creates new {@link Month}. * - * @param expression must not be {@literal null}. + * @param fieldReference must not be {@literal null}. * @return */ - public static Quarter quarterOf(AggregationExpression expression) { - return quarterOf(expression, null); + public static Month monthOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return month(Fields.field(fieldReference)); } /** - * Creates new {@link Quarter} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link Month}. * * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 * @return */ - public static Quarter quarterOf(AggregationExpression expression, @Nullable Object timezone) { + public static Month monthOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); - return new Quarter(expression, timezone); + return month(expression); } /** - * Creates new {@link Quarter} for current date in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. If Mongo is less than 3.6, - * timezone must be null. + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param dateFactory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. (supports Mongo prior to 3.6 if null) + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link Month}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. * @since 2.1 - * @return */ - public static Quarter quarterOf(DateFactory dateFactory, @Nullable Object timezone) { + @Override + public Month withTimezone(Timezone timezone) { - Assert.notNull(dateFactory, "dateFactory must not be null!"); - return new Quarter(dateFactory, timezone); + Assert.notNull(timezone, "Timezone must not be null."); + return new Month(appendTimezone(values().iterator().next(), timezone)); } @Override - public Document toDocument(AggregationOperationContext context) { - return cond.toDocument(context); + protected String getMongoMethod() { + return "$month"; } } /** - * {@link AggregationExpression} for {@code $month}. + * {@link AggregationExpression} for {@code $week}. * * @author Christoph Strobl - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/month/ + * @author Matt Morrissette */ - public static class Month extends DateAggregationExpression { - - private Month(Object value, Object timezone) { - super(value, timezone); - } + public static class Week extends TimezonedDateAggregationExpression { - @Override - protected String getMongoMethod() { - return "$month"; + private Week(Object value) { + super(value); } /** - * Creates new {@link Month} in the UTC timezone. + * Creates new {@link Week}. * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Month monthOf(String fieldReference) { - return monthOf(fieldReference, null); - } - - /** - * Creates new {@link Month} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. + * @param value must not be {@literal null} and resolve to field, expression or object that represents a date. + * @return new instance of {@link Week}. + * @throws IllegalArgumentException if given value is {@literal null}. * @since 2.1 - * @return */ - public static Month monthOf(String fieldReference, @Nullable Object timezone) { + public static Week week(Object value) { - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new Month(Fields.field(fieldReference), timezone); + Assert.notNull(value, "value must not be null!"); + return new Week(value); } /** - * Creates new {@link Month} in the UTC timezone. + * Creates new {@link Week}. * - * @param expression must not be {@literal null}. + * @param fieldReference must not be {@literal null}. * @return */ - public static Month monthOf(AggregationExpression expression) { - return monthOf(expression, null); + public static Week weekOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return week(Fields.field(fieldReference)); } /** - * Creates new {@link Month} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link Week}. * * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 * @return */ - public static Month monthOf(AggregationExpression expression, @Nullable Object timezone) { + public static Week weekOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); - return new Month(expression, timezone); + return week(expression); } /** - * Creates new {@link Month} for current date in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. If Mongo is less than 3.6, - * timezone must be null. + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param dateFactory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. (supports Mongo prior to 3.6 if null) + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link Week}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. * @since 2.1 - * @return */ - public static Month monthOf(DateFactory dateFactory, @Nullable Object timezone) { + @Override + public Week withTimezone(Timezone timezone) { + + Assert.notNull(timezone, "Timezone must not be null."); + return new Week(appendTimezone(values().iterator().next(), timezone)); + } - Assert.notNull(dateFactory, "dateFactory must not be null!"); - return new Month(dateFactory, timezone); + @Override + protected String getMongoMethod() { + return "$week"; } } /** - * {@link AggregationExpression} for {@code $week}. This behavior is the same as the “%U” operator to the strftime + * {@link AggregationExpression} for {@code $hour}. * * @author Christoph Strobl - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/week/ + * @author Matt Morrissette */ - public static class Week extends DateAggregationExpression { - - private Week(Object value, Object timezone) { - super(value, timezone); - } - - @Override - protected String getMongoMethod() { - return "$week"; - } + public static class Hour extends TimezonedDateAggregationExpression { - /** - * Creates new {@link Week} in the UTC timezone. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Week weekOf(String fieldReference) { - return weekOf(fieldReference, null); + private Hour(Object value) { + super(value); } /** - * Creates new {@link Week} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link Hour}. * - * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. + * @param value must not be {@literal null} and resolve to field, expression or object that represents a date. + * @return new instance of {@link Hour}. + * @throws IllegalArgumentException if given value is {@literal null}. * @since 2.1 - * @return */ - public static Week weekOf(String fieldReference, @Nullable Object timezone) { + public static Hour hour(Object value) { - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new Week(Fields.field(fieldReference), timezone); + Assert.notNull(value, "value must not be null!"); + return new Hour(value); } /** - * Creates new {@link Week} in the UTC timezone. + * Creates new {@link Hour}. * - * @param expression must not be {@literal null}. + * @param fieldReference must not be {@literal null}. * @return */ - public static Week weekOf(AggregationExpression expression) { - return weekOf(expression, null); + public static Hour hourOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return hour(Fields.field(fieldReference)); } /** - * Creates new {@link Week} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link Hour}. * * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 * @return */ - public static Week weekOf(AggregationExpression expression, @Nullable Object timezone) { + public static Hour hourOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); - return new Week(expression, timezone); + return hour(expression); } /** - * Creates new {@link Week} for current date in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. If Mongo is less than 3.6, - * timezone must be null. + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param dateFactory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. (supports Mongo prior to 3.6 if null) + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link Hour}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. * @since 2.1 - * @return */ - public static Week weekOf(DateFactory dateFactory, @Nullable Object timezone) { + @Override + public Hour withTimezone(Timezone timezone) { + + Assert.notNull(timezone, "Timezone must not be null."); + return new Hour(appendTimezone(values().iterator().next(), timezone)); + } - Assert.notNull(dateFactory, "dateFactory must not be null!"); - return new Week(dateFactory, timezone); + @Override + protected String getMongoMethod() { + return "$hour"; } } /** - * {@link AggregationExpression} for {@code $hour}. + * {@link AggregationExpression} for {@code $minute}. * * @author Christoph Strobl - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/hour/ + * @author Matt Morrissette */ - public static class Hour extends DateAggregationExpression { - - private Hour(Object value, Object timezone) { - super(value, timezone); - } - - @Override - protected String getMongoMethod() { - return "$hour"; - } + public static class Minute extends TimezonedDateAggregationExpression { - /** - * Creates new {@link Hour} in the UTC timezone. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Hour hourOf(String fieldReference) { - return hourOf(fieldReference, null); + private Minute(Object value) { + super(value); } /** - * Creates new {@link Hour} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link Minute}. * - * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. + * @param value must not be {@literal null} and resolve to field, expression or object that represents a date. + * @return new instance of {@link Minute}. + * @throws IllegalArgumentException if given value is {@literal null}. * @since 2.1 - * @return */ - public static Hour hourOf(String fieldReference, @Nullable Object timezone) { + public static Minute minute(Object value) { - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new Hour(Fields.field(fieldReference), timezone); + Assert.notNull(value, "value must not be null!"); + return new Minute(value); } /** - * Creates new {@link Hour} in the UTC timezone. + * Creates new {@link Minute}. * - * @param expression must not be {@literal null}. + * @param fieldReference must not be {@literal null}. * @return */ - public static Hour hourOf(AggregationExpression expression) { - return hourOf(expression, null); + public static Minute minuteOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return minute(Fields.field(fieldReference)); } /** - * Creates new {@link Hour} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link Minute}. * * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 * @return */ - public static Hour hourOf(AggregationExpression expression, @Nullable Object timezone) { + public static Minute minuteOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); - return new Hour(expression, timezone); + return minute(expression); } /** - * Creates new {@link Hour} for current date in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. If Mongo is less than 3.6, - * timezone must be null. + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param dateFactory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. (supports Mongo prior to 3.6 if null) + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link Minute}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. * @since 2.1 - * @return */ - public static Hour hourOf(DateFactory dateFactory, @Nullable Object timezone) { + @Override + public Minute withTimezone(Timezone timezone) { + + Assert.notNull(timezone, "Timezone must not be null."); + return new Minute(appendTimezone(values().iterator().next(), timezone)); + } - Assert.notNull(dateFactory, "dateFactory must not be null!"); - return new Hour(dateFactory, timezone); + @Override + protected String getMongoMethod() { + return "$minute"; } } /** - * {@link AggregationExpression} for {@code $minute}. + * {@link AggregationExpression} for {@code $second}. * * @author Christoph Strobl - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/minute/ + * @author Matt Morrissette */ - public static class Minute extends DateAggregationExpression { - - private Minute(Object value, Object timezone) { - super(value, timezone); - } - - @Override - protected String getMongoMethod() { - return "$minute"; - } + public static class Second extends TimezonedDateAggregationExpression { - /** - * Creates new {@link Minute} in the UTC timezone. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Minute minuteOf(String fieldReference) { - return minuteOf(fieldReference, null); + private Second(Object value) { + super(value); } /** - * Creates new {@link Minute} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link Second}. * - * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. + * @param value must not be {@literal null} and resolve to field, expression or object that represents a date. + * @return new instance of {@link Second}. + * @throws IllegalArgumentException if given value is {@literal null}. * @since 2.1 - * @return */ - public static Minute minuteOf(String fieldReference, @Nullable Object timezone) { + public static Second second(Object value) { - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new Minute(Fields.field(fieldReference), timezone); + Assert.notNull(value, "value must not be null!"); + return new Second(value); } /** - * Creates new {@link Minute} in the UTC timezone. + * Creates new {@link Second}. * - * @param expression must not be {@literal null}. + * @param fieldReference must not be {@literal null}. * @return */ - public static Minute minuteOf(AggregationExpression expression) { - return minuteOf(expression, null); + public static Second secondOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return second(Fields.field(fieldReference)); } /** - * Creates new {@link Minute} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link Second}. * * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 * @return */ - public static Minute minuteOf(AggregationExpression expression, @Nullable Object timezone) { + public static Second secondOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); - return new Minute(expression, timezone); + return second(expression); } /** - * Creates new {@link Minute} for current date in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. If Mongo is less than 3.6, - * timezone must be null. + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param dateFactory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. (supports Mongo prior to 3.6 if null) + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link Second}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. * @since 2.1 - * @return */ - public static Minute minuteOf(DateFactory dateFactory, @Nullable Object timezone) { + @Override + public Second withTimezone(Timezone timezone) { - Assert.notNull(dateFactory, "dateFactory must not be null!"); - return new Minute(dateFactory, timezone); + Assert.notNull(timezone, "Timezone must not be null."); + return new Second(appendTimezone(values().iterator().next(), timezone)); + } + + @Override + protected String getMongoMethod() { + return "$second"; } } /** - * {@link AggregationExpression} for {@code $second}. + * {@link AggregationExpression} for {@code $millisecond}. * * @author Christoph Strobl - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/second/ + * @author Matt Morrissette */ - public static class Second extends DateAggregationExpression { - - private Second(Object value, Object timezone) { - super(value, timezone); - } - - @Override - protected String getMongoMethod() { - return "$second"; - } + public static class Millisecond extends TimezonedDateAggregationExpression { - /** - * Creates new {@link Second} in the UTC timezone. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Second secondOf(String fieldReference) { - return secondOf(fieldReference, null); + private Millisecond(Object value) { + super(value); } /** - * Creates new {@link Second} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link Millisecond}. * - * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. + * @param value must not be {@literal null} and resolve to field, expression or object that represents a date. + * @return new instance of {@link Millisecond}. + * @throws IllegalArgumentException if given value is {@literal null}. * @since 2.1 - * @return */ - public static Second secondOf(String fieldReference, @Nullable Object timezone) { + public static Millisecond millisecond(Object value) { - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new Second(Fields.field(fieldReference), timezone); + Assert.notNull(value, "value must not be null!"); + return new Millisecond(value); } /** - * Creates new {@link Second} in the UTC timezone. + * Creates new {@link Millisecond}. * - * @param expression must not be {@literal null}. + * @param fieldReference must not be {@literal null}. * @return */ - public static Second secondOf(AggregationExpression expression) { - return secondOf(expression, null); + public static Millisecond millisecondOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return millisecond(Fields.field(fieldReference)); } /** - * Creates new {@link Second} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link Millisecond}. * * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 * @return */ - public static Second secondOf(AggregationExpression expression, @Nullable Object timezone) { + public static Millisecond millisecondOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); - return new Second(expression, timezone); + return millisecond(expression); } /** - * Creates new {@link Second} for current date in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. If Mongo is less than 3.6, - * timezone must be null. + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param dateFactory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. (supports Mongo prior to 3.6 if null) + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link Millisecond}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. * @since 2.1 - * @return */ - public static Second secondOf(DateFactory dateFactory, @Nullable Object timezone) { + @Override + public Millisecond withTimezone(Timezone timezone) { + + Assert.notNull(timezone, "Timezone must not be null."); + return new Millisecond(appendTimezone(values().iterator().next(), timezone)); + } - Assert.notNull(dateFactory, "dateFactory must not be null!"); - return new Second(dateFactory, timezone); + @Override + protected String getMongoMethod() { + return "$millisecond"; } } /** - * {@link AggregationExpression} for {@code $millisecond}. + * {@link AggregationExpression} for {@code $dateToString}. * * @author Christoph Strobl - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/millisecond/ + * @author Matt Morrissette */ - public static class Millisecond extends DateAggregationExpression { - - private Millisecond(Object value, Object timezone) { - super(value, timezone); - } + public static class DateToString extends TimezonedDateAggregationExpression { - @Override - protected String getMongoMethod() { - return "$millisecond"; + private DateToString(Object value) { + super(value); } /** - * Creates new {@link Millisecond} in the UTC timezone. + * Creates new {@link FormatBuilder}. * - * @param fieldReference must not be {@literal null}. - * @return + * @param value must not be {@literal null} and resolve to field, expression or object that represents a date. + * @return new instance of {@link FormatBuilder}. + * @throws IllegalArgumentException if given value is {@literal null}. + * @since 2.1 */ - public static Millisecond millisecondOf(String fieldReference) { - return millisecondOf(fieldReference, null); + public static FormatBuilder dateToString(Object value) { + + Assert.notNull(value, "value must not be null!"); + + return new FormatBuilder() { + + @Override + public DateToString toString(String format) { + + Assert.notNull(format, "Format must not be null!"); + return new DateToString(argumentMap(value, format, Timezone.none())); + } + }; } /** - * Creates new {@link Millisecond} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link FormatBuilder} allowing to define the date format to apply. * * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 * @return */ - public static Millisecond millisecondOf(String fieldReference, @Nullable Object timezone) { + public static FormatBuilder dateOf(final String fieldReference) { - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new Millisecond(Fields.field(fieldReference), timezone); + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return dateToString(Fields.field(fieldReference)); } /** - * Creates new {@link Millisecond} in the UTC timezone. + * Creates new {@link FormatBuilder} allowing to define the date format to apply. * * @param expression must not be {@literal null}. * @return */ - public static Millisecond millisecondOf(AggregationExpression expression) { - return millisecondOf(expression, null); + public static FormatBuilder dateOf(final AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return dateToString(expression); } /** - * Creates new {@link Millisecond} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link Millisecond}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. * @since 2.1 - * @return */ - public static Millisecond millisecondOf(AggregationExpression expression, @Nullable Object timezone) { + @Override + public DateToString withTimezone(Timezone timezone) { - Assert.notNull(expression, "Expression must not be null!"); - return new Millisecond(expression, timezone); + Assert.notNull(timezone, "Timezone must not be null."); + return new DateToString(argumentMap(get("date"), get("format"), timezone)); } - /** - * Creates new {@link Millisecond} for current date in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. If Mongo is less than 3.6, - * timezone must be null. - * - * @param dateFactory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. (supports Mongo prior to 3.6 if null) - * @since 2.1 - * @return - */ - public static Millisecond millisecondOf(DateFactory dateFactory, @Nullable Object timezone) { + @Override + protected String getMongoMethod() { + return "$dateToString"; + } + + private static java.util.Map argumentMap(Object date, String format, Timezone timezone) { + + java.util.Map args = new LinkedHashMap(2); + args.put("format", format); + args.put("date", date); + + if (!ObjectUtils.nullSafeEquals(timezone, Timezone.none())) { + args.put("timezone", timezone.value); + } + return args; + } + + public interface FormatBuilder { - Assert.notNull(dateFactory, "dateFactory must not be null!"); - return new Millisecond(dateFactory, timezone); + /** + * Creates new {@link DateToString} with all previously added arguments appending the given one. + * + * @param format must not be {@literal null}. + * @return + */ + DateToString toString(String format); } } @@ -1968,180 +1428,143 @@ public class DateOperators { * {@link AggregationExpression} for {@code $isoDayOfWeek}. * * @author Christoph Strobl - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/isoDayOfWeek/ + * @author Matt Morrissette */ - public static class IsoDayOfWeek extends DateAggregationExpression { - - private IsoDayOfWeek(Object value, Object timezone) { - super(value, timezone); - } + public static class IsoDayOfWeek extends TimezonedDateAggregationExpression { - @Override - protected String getMongoMethod() { - return "$isoDayOfWeek"; + private IsoDayOfWeek(Object value) { + super(value); } /** - * Creates new {@link IsoDayOfWeek} in the UTC timezone. + * Creates new {@link IsoDayOfWeek}. * - * @param fieldReference must not be {@literal null}. - * @return + * @param value must not be {@literal null} and resolve to field, expression or object that represents a date. + * @return new instance of {@link IsoDayOfWeek}. + * @throws IllegalArgumentException if given value is {@literal null}. + * @since 2.1 */ - public static IsoDayOfWeek isoDayOfWeek(String fieldReference) { - return isoDayOfWeek(fieldReference, null); + public static IsoDayOfWeek isoDayWeek(Object value) { + + Assert.notNull(value, "value must not be null!"); + return new IsoDayOfWeek(value); } /** - * Creates new {@link IsoDayOfWeek} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link IsoDayOfWeek}. * - * @since 2.1 * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. * @return */ - public static IsoDayOfWeek isoDayOfWeek(String fieldReference, @Nullable Object timezone) { + public static IsoDayOfWeek isoDayOfWeek(String fieldReference) { - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new IsoDayOfWeek(Fields.field(fieldReference), timezone); + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return isoDayWeek(Fields.field(fieldReference)); } /** - * Creates new {@link IsoDayOfWeek} in the UTC timezone. + * Creates new {@link IsoDayOfWeek}. * * @param expression must not be {@literal null}. * @return */ public static IsoDayOfWeek isoDayOfWeek(AggregationExpression expression) { - return isoDayOfWeek(expression, null); - } - - /** - * Creates new {@link IsoDayOfWeek} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 - * @return - */ - public static IsoDayOfWeek isoDayOfWeek(AggregationExpression expression, @Nullable Object timezone) { Assert.notNull(expression, "Expression must not be null!"); - return new IsoDayOfWeek(expression, timezone); + return isoDayWeek(expression); } /** - * Creates new {@link IsoDayOfWeek} for current date in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. If Mongo is less than 3.6, - * timezone must be null. + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param dateFactory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. (supports Mongo prior to 3.6 if null) + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link IsoDayOfWeek}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. * @since 2.1 - * @return */ - public static IsoDayOfWeek isoDayOfWeek(DateFactory dateFactory, @Nullable Object timezone) { + @Override + public IsoDayOfWeek withTimezone(Timezone timezone) { - Assert.notNull(dateFactory, "dateFactory must not be null!"); - return new IsoDayOfWeek(dateFactory, timezone); + Assert.notNull(timezone, "Timezone must not be null."); + return new IsoDayOfWeek(appendTimezone(values().iterator().next(), timezone)); } + @Override + protected String getMongoMethod() { + return "$isoDayOfWeek"; + } } /** * {@link AggregationExpression} for {@code $isoWeek}. * * @author Christoph Strobl - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/isoWeek/ + * @author Matt Morrissette */ - public static class IsoWeek extends DateAggregationExpression { - - private IsoWeek(Object value, Object timezone) { - super(value, timezone); - } + public static class IsoWeek extends TimezonedDateAggregationExpression { - @Override - protected String getMongoMethod() { - return "$isoWeek"; + private IsoWeek(Object value) { + super(value); } /** - * Creates new {@link IsoWeek} in the UTC timezone. + * Creates new {@link IsoWeek}. * - * @param fieldReference must not be {@literal null}. - * @return + * @param value must not be {@literal null} and resolve to field, expression or object that represents a date. + * @return new instance of {@link IsoWeek}. + * @throws IllegalArgumentException if given value is {@literal null}. + * @since 2.1 */ - public static IsoWeek isoWeekOf(String fieldReference) { - return isoWeekOf(fieldReference, null); + public static IsoWeek isoWeek(Object value) { + + Assert.notNull(value, "value must not be null!"); + return new IsoWeek(value); } /** - * Creates new {@link IsoWeek} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link IsoWeek}. * * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 * @return */ - public static IsoWeek isoWeekOf(String fieldReference, @Nullable Object timezone) { + public static IsoWeek isoWeekOf(String fieldReference) { - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new IsoWeek(Fields.field(fieldReference), timezone); + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return isoWeek(Fields.field(fieldReference)); } /** - * Creates new {@link IsoWeek} in the UTC timezone. + * Creates new {@link IsoWeek}. * * @param expression must not be {@literal null}. * @return */ public static IsoWeek isoWeekOf(AggregationExpression expression) { - return isoWeekOf(expression, null); - } - - /** - * Creates new {@link IsoWeek} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 - * @return - */ - public static IsoWeek isoWeekOf(AggregationExpression expression, @Nullable Object timezone) { Assert.notNull(expression, "Expression must not be null!"); - return new IsoWeek(expression, timezone); + return isoWeek(expression); } /** - * Creates new {@link IsoWeek} for current date in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. If Mongo is less than 3.6, - * timezone must be null. + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param dateFactory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. (supports Mongo prior to 3.6 if null) + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link IsoWeek}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. * @since 2.1 - * @return */ - public static IsoWeek isoWeekOf(DateFactory dateFactory, @Nullable Object timezone) { + @Override + public IsoWeek withTimezone(Timezone timezone) { + + Assert.notNull(timezone, "Timezone must not be null."); + return new IsoWeek(appendTimezone(values().iterator().next(), timezone)); + } - Assert.notNull(dateFactory, "dateFactory must not be null!"); - return new IsoWeek(dateFactory, timezone); + @Override + protected String getMongoMethod() { + return "$isoWeek"; } } @@ -2149,1090 +1572,712 @@ public class DateOperators { * {@link AggregationExpression} for {@code $isoWeekYear}. * * @author Christoph Strobl - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/isoWeekYear/ + * @author Matt Morrissette */ - public static class IsoWeekYear extends DateAggregationExpression { - - private IsoWeekYear(Object value, Object timezone) { - super(value, timezone); - } + public static class IsoWeekYear extends TimezonedDateAggregationExpression { - @Override - protected String getMongoMethod() { - return "$isoWeekYear"; + private IsoWeekYear(Object value) { + super(value); } /** - * Creates new {@link IsoWeekYear} in the UTC timezone. + * Creates new {@link IsoWeekYear}. * - * @param fieldReference must not be {@literal null}. - * @return + * @param value must not be {@literal null} and resolve to field, expression or object that represents a date. + * @return new instance of {@link IsoWeekYear}. + * @throws IllegalArgumentException if given value is {@literal null}. + * @since 2.1 */ - public static IsoWeekYear isoWeekYearOf(String fieldReference) { - return isoWeekYearOf(fieldReference, null); + public static IsoWeekYear isoWeekYear(Object value) { + + Assert.notNull(value, "value must not be null!"); + return new IsoWeekYear(value); } /** - * Creates new {@link IsoWeekYear} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Creates new {@link IsoWeekYear}. * * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 * @return */ - public static IsoWeekYear isoWeekYearOf(String fieldReference, @Nullable Object timezone) { + public static IsoWeekYear isoWeekYearOf(String fieldReference) { - Assert.hasText(fieldReference, "FieldReference must not be null!"); - return new IsoWeekYear(Fields.field(fieldReference), timezone); + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return isoWeekYear(Fields.field(fieldReference)); } /** - * Creates new {@link Millisecond} in the UTC timezone. + * Creates new {@link Millisecond}. * * @param expression must not be {@literal null}. * @return */ public static IsoWeekYear isoWeekYearOf(AggregationExpression expression) { - return isoWeekYearOf(expression, null); - } - - /** - * Creates new {@link Millisecond} in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 - * @return - */ - public static IsoWeekYear isoWeekYearOf(AggregationExpression expression, @Nullable Object timezone) { Assert.notNull(expression, "Expression must not be null!"); - return new IsoWeekYear(expression, timezone); + return isoWeekYear(expression); } /** - * Creates new {@link IsoWeekYear} for current date in the given timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. If Mongo is less than 3.6, - * timezone must be null. + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @param dateFactory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. (supports Mongo prior to 3.6 if null) + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link IsoWeekYear}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. * @since 2.1 - * @return */ - public static IsoWeekYear isoWeekYearOf(DateFactory dateFactory, @Nullable Object timezone) { + @Override + public IsoWeekYear withTimezone(Timezone timezone) { + + Assert.notNull(timezone, "Timezone must not be null."); + return new IsoWeekYear(appendTimezone(values().iterator().next(), timezone)); + } - Assert.notNull(dateFactory, "dateFactory must not be null!"); - return new IsoWeekYear(dateFactory, timezone); + @Override + protected String getMongoMethod() { + return "$isoWeekYear"; } } /** - * {@link AggregationExpression} for {@code $dateToString}. - * * @author Christoph Strobl - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/dateToString/ + * @since 2.1 */ - public static class DateToString extends AbstractAggregationExpression { - - private DateToString(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$dateToString"; - } + public interface DateParts> { /** - * Creates new {@link FormatBuilder} allowing to define the date format to apply in the UTC timezone + * Set the {@literal hour} to the given value which must resolve to a value in range of {@code 0 - 23}. Can be a + * simple value, {@link Field field reference} or {@link AggregationExpression expression}. * - * @param fieldReference must not be {@literal null}. - * @return + * @param hour must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal hour} is {@literal null} */ - public static FormatBuilder dateOf(final String fieldReference) { - return dateOf(fieldReference, null); - } + T hour(Object hour); /** - * Creates new {@link FormatBuilder} allowing to define the date format to apply in the specified timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Set the {@literal hour} to the value resolved by following the given {@link Field field reference}. * * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 - * @return + * @return new instance. + * @throws IllegalArgumentException if given {@literal fieldReference} is {@literal null}. */ - public static FormatBuilder dateOf(final String fieldReference, @Nullable Object timezone) { - - Assert.hasText(fieldReference, "FieldReference must not be null!"); - - return new FormatBuilder() { - - @Override - public DateToString toString(String format) { - return toString(format, timezone); - } - - @Override - public DateToString toString(String format, @Nullable Object timezone) { - - Assert.notNull(format, "Format must not be null!"); - return new DateToString(argumentMap(Fields.field(fieldReference), format, timezone)); - } - }; + default T hourOf(String fieldReference) { + return hour(Fields.field(fieldReference)); } /** - * Creates new {@link FormatBuilder} allowing to define the date format to apply in the UTC timezone. + * Set the {@literal hour} to the result of the given {@link AggregationExpression expression}. * * @param expression must not be {@literal null}. - * @return + * @return new instance. + * @throws IllegalArgumentException if given {@literal expression} is {@literal null}. */ - public static FormatBuilder dateOf(final AggregationExpression expression) { - return dateOf(expression, null); + default T hourOf(AggregationExpression expression) { + return hour(expression); } /** - * Creates new {@link FormatBuilder} allowing to define the date format to apply in the specified timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. + * Set the {@literal minute} to the given value which must resolve to a value in range {@code 0 - 59}. Can be a + * simple value, {@link Field field reference} or {@link AggregationExpression expression}. * - * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @since 2.1 - * @return + * @param minute must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal minute} is {@literal null} */ - public static FormatBuilder dateOf(final AggregationExpression expression, @Nullable Object timezone) { - - Assert.notNull(expression, "Expression must not be null!"); - Assert.isTrue(DateAggregationExpression.isValidTimezoneObject(timezone), - () -> "Timezone was not a valid timezone: " + timezone + ". Must be String, AggregationExpression or Field"); - - return new FormatBuilder() { - - @Override - public DateToString toString(String format) { - return toString(format, timezone); - } - - @Override - public DateToString toString(String format, @Nullable Object timezone) { - Assert.notNull(format, "Format must not be null!"); - return new DateToString(argumentMap(expression, format, timezone)); - } - }; - } + T minute(Object minute); /** - * Creates new {@link FormatBuilder} allowing to define the date format to apply in the specified timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. If Mongo is less than 3.6, - * timezone must be null. + * Set the {@literal minute} to the value resolved by following the given {@link Field field reference}. * - * @param dateFactory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. (supports Mongo prior to 3.6 if null) - * @since 2.1 - * @return + * @param fieldReference must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal fieldReference} is {@literal null}. */ - public static FormatBuilder dateOf(final DateFactory dateFactory, @Nullable Object timezone) { - - Assert.notNull(dateFactory, "CurrentDateFactory must not be null!"); - Assert.isTrue(DateAggregationExpression.isValidTimezoneObject(timezone), - () -> "Timezone was not a valid timezone: " + timezone + ". Must be String, AggregationExpression or Field"); - - return new FormatBuilder() { - - @Override - public DateToString toString(String format) { - return toString(format, timezone); - } - - @Override - public DateToString toString(String format, @Nullable Object timezone) { - Assert.notNull(format, "Format must not be null!"); - return new DateToString(argumentMap(dateFactory, format, timezone)); - } - }; - } - - private static java.util.Map argumentMap(Object date, String format, @Nullable Object timezone) { - - java.util.Map args = new LinkedHashMap<>(5); - args.put("date", date); - args.put("format", format); - if (timezone != null) { - Assert.isTrue(DateAggregationExpression.isValidTimezoneObject(timezone), - () -> "Timezone was not a valid timezone: " + timezone - + ". Must be String, AggregationExpression or Field"); - args.put("timezone", timezone); - } - return args; - } - - public interface FormatBuilder { - - /** - * Creates new {@link DateToString} with all previously added arguments appending the given one in the builder - * timezone (default UTC). - * - * @param format must not be {@literal null}. - * @return - */ - DateToString toString(String format); - - /** - * Creates new {@link DateToString} with all previously added arguments appending the given one in the given - * timezone. - *

- * WARNING: Mongo 3.6+ only. Using timezone on prior Mongo versions will cause errors. - * - * @param format must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} - * or {@link Field}. If null, UTC is assumed. - * @since 2.1 - * @return - */ - DateToString toString(String format, @Nullable Object timezone); - } - } - - /** - * {@link AggregationExpression} for {@code $dateFromString}. - *

- * WARNING: Mongo 3.6+ only. - * - * @since 2.1 - * @author Matt Morrissette - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromString/ - */ - public static class DateFromString extends AbstractAggregationExpression { - - private DateFromString(Object value, Object timezone) { - super(argumentMap(value, timezone)); - } - - private static Map argumentMap(final Object value, final Object timezone) { - - final Map vals = new LinkedHashMap<>(4); - vals.put("dateString", value); - if (timezone != null) { - Assert.isTrue(DateAggregationExpression.isValidTimezoneObject(timezone), - () -> "Timezone was not a valid timezone: " + timezone - + ". Must be String, AggregationExpression or Field"); - vals.put("timezone", timezone); - } - return vals; + default T minuteOf(String fieldReference) { + return minute(Fields.field(fieldReference)); } - @Override - protected String getMongoMethod() { - return "$dateFromString"; + /** + * Set the {@literal minute} to the result of the given {@link AggregationExpression expression}. + * + * @param expression must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal expression} is {@literal null}. + */ + default T minuteOf(AggregationExpression expression) { + return minute(expression); } /** - * Creates new {@link DateFromString} in the UTC timezone for a date referencing a field. + * Set the {@literal second} to the given value which must resolve to a value in range {@code 0 - 59}. Can be a + * simple value, {@link Field field reference} or {@link AggregationExpression expression}. * - * @param fieldReference must not be {@literal null}. - * @return + * @param second must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal second} is {@literal null} */ - public static DateFromString dateFromString(final String fieldReference) { - return dateFromString(fieldReference, null); - } + T second(Object second); /** - * Creates new {@link DateFromString} in the given timezone for a date referencing a field. + * Set the {@literal second} to the value resolved by following the given {@link Field field reference}. * * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @return + * @return new instance. + * @throws IllegalArgumentException if given {@literal fieldReference} is {@literal null}. */ - public static DateFromString dateFromString(final String fieldReference, @Nullable final Object timezone) { - - Assert.notNull(fieldReference, "fieldReference must not be null!"); - return new DateFromString(Fields.field(fieldReference), timezone); + default T secondOf(String fieldReference) { + return second(Fields.field(fieldReference)); } /** - * Creates new {@link DateFromString} in the given timezone for a date from evaluating an AggregationExpression. + * Set the {@literal second} to the result of the given {@link AggregationExpression expression}. * * @param expression must not be {@literal null}. - * @return + * @return new instance. + * @throws IllegalArgumentException if given {@literal expression} is {@literal null}. */ - public static DateFromString dateFromString(final AggregationExpression expression) { - return dateFromString(expression, null); + default T secondOf(AggregationExpression expression) { + return second(expression); } /** - * Creates new {@link DateFromString} in the given timezone for a date from evaluating an AggregationExpression. + * Set the {@literal milliseconds} to the given value which must resolve to a value in range {@code 0 - 999}. Can be + * a simple value, {@link Field field reference} or {@link AggregationExpression expression}. * - * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @return + * @param milliseconds must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal milliseconds} is {@literal null} */ - public static DateFromString dateFromString(final AggregationExpression expression, - @Nullable final Object timezone) { - - Assert.notNull(expression, "expression must not be null!"); - return new DateFromString(expression, timezone); - } + T milliseconds(Object milliseconds); /** - * Creates new {@link DateFromString} in the given timezone for a date provided by the given factory. + * Set the {@literal milliseconds} to the value resolved by following the given {@link Field field reference}. * - * @param factory must not be {@literal null}. - * @return + * @param fieldReference must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal fieldReference} is {@literal null}. */ - public static DateFromString dateFromString(final DateFactory factory) { - return dateFromString(factory, null); + default T millisecondsOf(String fieldReference) { + return milliseconds(Fields.field(fieldReference)); } /** - * Creates new {@link DateFromString} in the given timezone for a date provided by the given factory. + * Set the {@literal milliseconds} to the result of the given {@link AggregationExpression expression}. * - * @param factory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @return + * @param expression must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal expression} is {@literal null}. */ - public static DateFromString dateFromString(final DateFactory factory, final @Nullable Object timezone) { - - Assert.notNull(factory, "factory must not be null!"); - return new DateFromString(factory, timezone); + default T millisecondsOf(AggregationExpression expression) { + return milliseconds(expression); } } /** - * {@link AggregationExpression} for {@code $dateToParts}. - *

- * WARNING: Mongo 3.6+ only. + * {@link AggregationExpression} for {@code $dateFromParts}.
+ * NOTE: Requires MongoDB 3.6 or later. * - * @since 2.1 * @author Matt Morrissette - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/dateToParts/ + * @author Christoph Strobl + * @see https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/ + * @since 2.1 */ - public static class DateToParts extends AbstractAggregationExpression { - - private DateToParts(Object value, Object timezone, Boolean iso8601) { - super(argumentMap(value, timezone, iso8601)); - } - - private static Map argumentMap(final Object value, Object timezone, Boolean iso8601) { - - final Map vals = new LinkedHashMap<>(6); - vals.put("date", value); - if (timezone != null) { - if (timezone instanceof Boolean) { - // iso8601 passed as second argument - if (iso8601 == null) { - iso8601 = (Boolean) timezone; - } else { - throw new IllegalArgumentException( - "Timezone was not a valid timezone: " + timezone + ". Must be String, AggregationExpression or Field"); - } - } else { - Assert.isTrue(DateAggregationExpression.isValidTimezoneObject(timezone), - () -> "Timezone was not a valid timezone: " + timezone - + ". Must be String, AggregationExpression or Field"); - vals.put("timezone", timezone); - } - } - if (iso8601 != null) { - vals.put("iso8601", iso8601); - } - return vals; - } + public static class DateFromParts extends TimezonedDateAggregationExpression implements DateParts { - @Override - protected String getMongoMethod() { - return "$dateToParts"; + private DateFromParts(Object value) { + super(value); } /** - * Creates new {@link DateToParts} in the UTC timezone. + * Creates new {@link DateFromPartsWithYear}. * - * @param fieldReference must not be {@literal null}. - * @return + * @return new instance of {@link DateFromPartsWithYear}. + * @since 2.1 */ - public static DateToParts dateToParts(final String fieldReference) { - return dateToParts(fieldReference, null, null); + public static DateFromPartsWithYear dateFromParts() { + return year -> new DateFromParts(Collections.singletonMap("year", year)); } /** - * Creates new {@link DateToParts} in the given timezone. + * Set the {@literal month} to the given value which must resolve to a calendar month in range {@code 1 - 12}. Can + * be a simple value, {@link Field field reference} or {@link AggregationExpression expression}. * - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @param fieldReference must not be {@literal null}. - * @return + * @param month must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal month} is {@literal null}. */ - public static DateToParts dateToParts(final String fieldReference, final Object timezone) { - return dateToParts(fieldReference, timezone, null); + public DateFromParts month(Object month) { + return new DateFromParts(append("month", month)); } /** - * Creates new {@link DateToParts} in the given timezone. + * Set the {@literal month} to the value resolved by following the given {@link Field field reference}. * * @param fieldReference must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @param iso8601 If set to true, modifies the output document to use ISO week date fields. Defaults to false. - * @return + * @return new instance. + * @throws IllegalArgumentException if given {@literal fieldReference} is {@literal null}. */ - public static DateToParts dateToParts(final String fieldReference, @Nullable final Object timezone, - @Nullable final Boolean iso8601) { - - Assert.notNull(fieldReference, "fieldReference must not be null!"); - return new DateToParts(Fields.field(fieldReference), timezone, iso8601); + public DateFromParts monthOf(String fieldReference) { + return month(Fields.field(fieldReference)); } /** - * Creates new {@link DateToParts} in the UTC timezone. + * Set the {@literal month} to the result of the given {@link AggregationExpression expression}. * * @param expression must not be {@literal null}. - * @return + * @return new instance. + * @throws IllegalArgumentException if given {@literal expression} is {@literal null}. */ - public static DateToParts dateToParts(final AggregationExpression expression) { - return dateToParts(expression, null, null); + public DateFromParts monthOf(AggregationExpression expression) { + return month(expression); } /** - * Creates new {@link DateToParts} in the given timezone. + * Set the {@literal day} to the given value which must resolve to a calendar day in range {@code 1 - 31}. Can be a + * simple value, {@link Field field reference} or {@link AggregationExpression expression}. * - * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @return + * @param day must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal day} is {@literal null}. */ - public static DateToParts dateToParts(final AggregationExpression expression, @Nullable final Object timezone) { - return dateToParts(expression, timezone, null); + public DateFromParts day(Object day) { + return new DateFromParts(append("day", day)); } /** - * Creates new {@link DateToParts} in the given timezone. + * Set the {@literal day} to the value resolved by following the given {@link Field field reference}. * - * @param expression must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @param iso8601 If set to true, modifies the output document to use ISO week date fields. Defaults to false. - * @return + * @param fieldReference must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal fieldReference} is {@literal null}. */ - public static DateToParts dateToParts(final AggregationExpression expression, @Nullable final Object timezone, - final Boolean iso8601) { - - Assert.notNull(expression, "expression must not be null!"); - return new DateToParts(expression, timezone, iso8601); + public DateFromParts dayOf(String fieldReference) { + return day(Fields.field(fieldReference)); } /** - * Creates new {@link DateToParts} in the UTC timezone. + * Set the {@literal day} to the result of the given {@link AggregationExpression expression}. * - * @param factory must not be {@literal null}. - * @return + * @param expression must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal expression} is {@literal null}. */ - public static DateToParts dateToParts(final DateFactory factory) { - return dateToParts(factory, null, null); + public DateFromParts dayOf(AggregationExpression expression) { + return day(expression); } - /** - * Creates new {@link DateToParts} in the given timezone. - * - * @param factory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @return - */ - public static DateToParts dateToParts(final DateFactory factory, final @Nullable Object timezone) { - return dateToParts(factory, timezone, null); + @Override + public DateFromParts hour(Object hour) { + return new DateFromParts(append("hour", hour)); } - /** - * Creates new {@link DateToParts} in the given timezone. - * - * @param factory must not be {@literal null}. - * @param timezone nullable. The timezone ID or offset as a String. Also accepts a {@link AggregationExpression} or - * {@link Field}. If null, UTC is assumed. - * @param iso8601 If set to true, modifies the output document to use ISO week date fields. Defaults to false. - * @return - */ - public static DateToParts dateToParts(final DateFactory factory, final @Nullable Object timezone, - @Nullable final Boolean iso8601) { - - Assert.notNull(factory, "factory must not be null!"); - return new DateToParts(factory, timezone, iso8601); + @Override + public DateFromParts minute(Object minute) { + return new DateFromParts(append("minute", minute)); } - } - - /** - * AggregationExpression for '$dateFromParts' - * - * @author matt.morrissette - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/ - */ - public static class DateFromParts extends AbstractAggregationExpression { - - private DateFromParts(boolean isoWeek, Object yearOrIsoWeekYear, Object monthOrIsoWeek, Object dayOrIsoDayOfWeek, - Object hour, Object minute, Object second, Object millisecond, Object timezone) { - super(isoWeek - ? isoWeekMap(yearOrIsoWeekYear, monthOrIsoWeek, dayOrIsoDayOfWeek, hour, minute, second, millisecond, - timezone) - : calMap(yearOrIsoWeekYear, monthOrIsoWeek, dayOrIsoDayOfWeek, hour, minute, second, millisecond, timezone)); + @Override + public DateFromParts second(Object second) { + return new DateFromParts(append("second", second)); } @Override - protected String getMongoMethod() { - return "$dateFromParts"; + public DateFromParts milliseconds(Object milliseconds) { + return new DateFromParts(append("milliseconds", milliseconds)); } /** - * @return a new builder for {@link DateFromParts} using year/month/day - *

- * year is required - *

- * Timezone defaults to UTC if not specified - *

- * year and month default to 1 if not specified. All other fields default to 0. + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. + * + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link DateFromParts}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. */ - public static CalendarDatePartsBuilder fromParts() { - return new CalendarDatePartsBuilder(); + @Override + public DateFromParts withTimezone(Timezone timezone) { + return new DateFromParts(appendTimezone(argumentMap(), timezone)); } - /** - * @return a new builder for {@link DateFromParts} using isoWeekYear/isoWeek/isoDayOfWeek - *

- * isoWeekYear is required - *

- * Timezone defaults to UTC if not specified - *

- * isoWeek and isoDayOfWeek default to 1 if not specified. All other fields default to 0. - */ - public static IsoWeekDatePartsBuilder fromIsoWeekParts() { - return new IsoWeekDatePartsBuilder(); + @Override + protected String getMongoMethod() { + return "$dateFromParts"; } /** - * A Mutable builder to create a {@link DateFromParts} aggregation expression. All methods mutate this builder (they - * all return this for convenience) - * - * @param The concrete builder (either {@link CalendarDatePartsBuilder} for calendar date (i.e. - * year/month/day) or {@link IsoWeekDatePartsBuilder} for ISO week 8601 dates - * (isoWeekYear/isoWeek/isoDayOfWeek) + * @author Christoph Strobl */ - public abstract static class DatePartsBuilder> { - - protected Object hour; - - protected Object minute; - - protected Object second; - - protected Object millisecond; - - protected Object timezone; + public interface DateFromPartsWithYear { /** - * Sets the 'hour' of the {@link DateFromParts} to given the fixed value + * Set the {@literal year} to the given value which must resolve to a calendar year. Can be a simple value, + * {@link Field field reference} or {@link AggregationExpression expression}. * - * @param hour the fixed value to bind the field - * @return + * @param year must not be {@literal null}. + * @return new instance of {@link DateFromParts}. + * @throws IllegalArgumentException if given {@literal year} is {@literal null} */ - @SuppressWarnings("unchecked") - public Builder hour(Number hour) { - - this.hour = hour; - return (Builder) this; - } + DateFromParts year(Object year); /** - * Sets the 'hour' of the {@link DateFromParts} to given the field + * Set the {@literal year} to the value resolved by following the given {@link Field field reference}. * - * @param hour the field to read the 'hour' value from - * @return + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link DateFromParts}. + * @throws IllegalArgumentException if given {@literal fieldReference} is {@literal null}. */ - @SuppressWarnings("unchecked") - public Builder hourOf(String hour) { + default DateFromParts yearOf(String fieldReference) { - this.hour = Fields.field(hour); - return (Builder) this; + Assert.hasText(fieldReference, "Field reference must not be null nor empty."); + return year(Fields.field(fieldReference)); } /** - * Sets the 'hour' of the {@link DateFromParts} to given the fixed value + * Set the {@literal year} to the result of the given {@link AggregationExpression expression}. * - * @param hour the expression to evaluate the 'hour' value - * @return + * @param expression must not be {@literal null}. + * @return new instance of {@link DateFromParts}. + * @throws IllegalArgumentException if given {@literal expression} is {@literal null}. */ - @SuppressWarnings("unchecked") - public Builder hourOf(AggregationExpression hour) { + default DateFromParts yearOf(AggregationExpression expression) { - this.hour = hour; - return (Builder) this; + Assert.notNull(expression, "Expression must not be null!"); + return year(expression); } + } + } - /** - * Sets the 'minute' of the {@link DateFromParts} to given the fixed value - * - * @param minute the fixed value to bind the field - * @return - */ - @SuppressWarnings("unchecked") - public Builder minute(Number minute) { - - this.minute = minute; - return (Builder) this; - } + /** + * {@link AggregationExpression} for {@code $dateFromParts} using ISO week date.
+ * NOTE: Requires MongoDB 3.6 or later. + * + * @author Matt Morrissette + * @author Christoph Strobl + * @see https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromParts/ + * @since 2.1 + */ + public static class IsoDateFromParts extends TimezonedDateAggregationExpression + implements DateParts { - /** - * Sets the 'minute' of the {@link DateFromParts} to given the field - * - * @param minute the field to read the 'minute' value from - * @return - */ - @SuppressWarnings("unchecked") - public Builder minuteOf(String minute) { + private IsoDateFromParts(Object value) { + super(value); + } - this.minute = Fields.field(minute); - return (Builder) this; - } + /** + * Creates new {@link IsoDateFromPartsWithYear}. + * + * @return new instance of {@link IsoDateFromPartsWithYear}. + * @since 2.1 + */ + public static IsoDateFromPartsWithYear dateFromParts() { + return year -> new IsoDateFromParts(Collections.singletonMap("isoWeekYear", year)); + } - /** - * Sets the 'minute' of the {@link DateFromParts} to given the fixed value - * - * @param minute the expression to evaluate the 'minute' value - * @return - */ - @SuppressWarnings("unchecked") - public Builder minuteOf(AggregationExpression minute) { - - this.minute = minute; - return (Builder) this; - } - - /** - * Sets the 'second' of the {@link DateFromParts} to given the fixed value - * - * @param second the fixed value to bind the field - * @return - */ - @SuppressWarnings("unchecked") - public Builder second(Number second) { - - this.second = second; - return (Builder) this; - } - - /** - * Sets the 'second' of the {@link DateFromParts} to given the field - * - * @param second the field to read the 'second' value from - * @return - */ - @SuppressWarnings("unchecked") - public Builder secondOf(String second) { - - this.second = Fields.field(second); - return (Builder) this; - } - - /** - * Sets the 'second' of the {@link DateFromParts} to given the fixed value - * - * @param second the expression to evaluate the 'second' value - * @return - */ - @SuppressWarnings("unchecked") - public Builder secondOf(AggregationExpression second) { - - this.second = second; - return (Builder) this; - } - - /** - * Sets the 'millisecond' of the {@link DateFromParts} to given the fixed value - * - * @param millisecond the fixed value to bind the field - * @return - */ - @SuppressWarnings("unchecked") - public Builder millisecond(Number millisecond) { - - this.millisecond = millisecond; - return (Builder) this; - } - - /** - * Sets the 'millisecond' of the {@link DateFromParts} to given the field - * - * @param millisecond the field to read the 'millisecond' value from - * @return - */ - @SuppressWarnings("unchecked") - public Builder millisecondOf(String millisecond) { - - this.millisecond = Fields.field(millisecond); - return (Builder) this; - } - - /** - * Sets the 'millisecond' of the {@link DateFromParts} to given the fixed value - * - * @param millisecond the expression to evaluate the 'millisecond' value - * @return - */ - @SuppressWarnings("unchecked") - public Builder millisecondOf(AggregationExpression millisecond) { - - this.millisecond = millisecond; - return (Builder) this; - } - - /** - * Sets the 'timezone' of the {@link DateFromParts} to given the fixed value - * - * @param timezone the fixed value to bind the field - * @return - */ - @SuppressWarnings("unchecked") - public Builder timezone(String timezone) { - - this.timezone = timezone; - return (Builder) this; - } - - /** - * Sets the 'timezone' of the {@link DateFromParts} to given the field - * - * @param timezone the field to read the 'timezone' value from - * @return - */ - @SuppressWarnings("unchecked") - public Builder timezoneOf(String timezone) { - - this.timezone = Fields.field(timezone); - return (Builder) this; - } - - /** - * Sets the 'timezone' of the {@link DateFromParts} to given the fixed value - * - * @param timezone the expression to evaluate the 'timezone' value - * @return - */ - @SuppressWarnings("unchecked") - public Builder timezoneOf(AggregationExpression timezone) { - - this.timezone = timezone; - return (Builder) this; - } - - public abstract DateFromParts toDate(); + /** + * Set the {@literal week of year} to the given value which must resolve to a calendar week in range {@code 1 - 53}. + * Can be a simple value, {@link Field field reference} or {@link AggregationExpression expression}. + * + * @param isoWeek must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal isoWeek} is {@literal null}. + */ + public IsoDateFromParts isoWeek(Object isoWeek) { + return new IsoDateFromParts(append("isoWeek", isoWeek)); } - public static class CalendarDatePartsBuilder extends DatePartsBuilder { - - private Object year; - - private Object month; - - private Object day; - - @Override - public DateFromParts toDate() { - return new DateFromParts(false, year, month, day, hour, minute, second, millisecond, timezone); - } - - /** - * Sets the 'year' of the {@link DateFromParts} to given the fixed value - * - * @param year the fixed value to bind the field - * @return - */ - public CalendarDatePartsBuilder year(Number year) { - - this.year = year; - return this; - } - - /** - * Sets the 'year' of the {@link DateFromParts} to given the field - * - * @param year the field to read the 'year' value from - * @return - */ - public CalendarDatePartsBuilder yearOf(String year) { - - this.year = Fields.field(year); - return this; - } - - /** - * Sets the 'year' of the {@link DateFromParts} to given the fixed value - * - * @param year the expression to evaluate the 'year' value - * @return - */ - public CalendarDatePartsBuilder yearOf(AggregationExpression year) { - - this.year = year; - return this; - } - - /** - * Sets the 'month' of the {@link DateFromParts} to given the fixed value - * - * @param month the fixed value to bind the field - * @return - */ - public CalendarDatePartsBuilder month(Number month) { - - this.month = month; - return this; - } - - /** - * Sets the 'month' of the {@link DateFromParts} to given the field - * - * @param month the field to read the 'month' value from - * @return - */ - public CalendarDatePartsBuilder monthOf(String month) { - - this.month = Fields.field(month); - return this; - } - - /** - * Sets the 'month' of the {@link DateFromParts} to given the fixed value - * - * @param month the expression to evaluate the 'month' value - * @return - */ - public CalendarDatePartsBuilder monthOf(AggregationExpression month) { - - this.month = month; - return this; - } - - /** - * Sets the 'day' of the {@link DateFromParts} to given the fixed value - * - * @param day the fixed value to bind the field - * @return - */ - public CalendarDatePartsBuilder day(Number day) { - - this.day = day; - return this; - } - - /** - * Sets the 'day' of the {@link DateFromParts} to given the field - * - * @param day the field to read the 'day' value from - * @return - */ - public CalendarDatePartsBuilder dayOf(String day) { + /** + * Set the {@literal week of year} to the value resolved by following the given {@link Field field reference}. + * + * @param fieldReference must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal fieldReference} is {@literal null}. + */ + public IsoDateFromParts isoWeekOf(String fieldReference) { + return isoWeek(Fields.field(fieldReference)); + } - this.day = Fields.field(day); - return this; - } + /** + * Set the {@literal week of year} to the result of the given {@link AggregationExpression expression}. + * + * @param expression must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal expression} is {@literal null}. + */ + public IsoDateFromParts isoWeekOf(AggregationExpression expression) { + return isoWeek(expression); + } - /** - * Sets the 'day' of the {@link DateFromParts} to given the fixed value - * - * @param day the expression to evaluate the 'day' value - * @return - */ - public CalendarDatePartsBuilder dayOf(AggregationExpression day) { + /** + * Set the {@literal day of week} to the given value which must resolve to a weekday in range {@code 1 - 7}. Can be + * a simple value, {@link Field field reference} or {@link AggregationExpression expression}. + * + * @param day must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal isoWeek} is {@literal null}. + */ + public IsoDateFromParts isoDayOfWeek(Object day) { + return new IsoDateFromParts(append("isoDayOfWeek", day)); + } - this.day = day; - return this; - } + /** + * Set the {@literal day of week} to the value resolved by following the given {@link Field field reference}. + * + * @param fieldReference must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal fieldReference} is {@literal null}. + */ + public IsoDateFromParts isoDayOfWeekOf(String fieldReference) { + return isoDayOfWeek(Fields.field(fieldReference)); + } + /** + * Set the {@literal day of week} to the result of the given {@link AggregationExpression expression}. + * + * @param expression must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal expression} is {@literal null}. + */ + public IsoDateFromParts isoDayOfWeekOf(AggregationExpression expression) { + return isoDayOfWeek(expression); } - public static class IsoWeekDatePartsBuilder extends DatePartsBuilder { + @Override + public IsoDateFromParts hour(Object hour) { + return new IsoDateFromParts(append("hour", hour)); + } - private Object isoWeekYear; + @Override + public IsoDateFromParts minute(Object minute) { + return new IsoDateFromParts(append("minute", minute)); + } - private Object isoWeek; + @Override + public IsoDateFromParts second(Object second) { + return new IsoDateFromParts(append("second", second)); + } - private Object isoDayOfWeek; + @Override + public IsoDateFromParts milliseconds(Object milliseconds) { + return new IsoDateFromParts(append("milliseconds", milliseconds)); + } - @Override - public DateFromParts toDate() { - return new DateFromParts(true, isoWeekYear, isoWeek, isoDayOfWeek, hour, minute, second, millisecond, timezone); - } + /** + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. + * + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link IsoDateFromParts}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. + */ + @Override + public IsoDateFromParts withTimezone(Timezone timezone) { + return new IsoDateFromParts(appendTimezone(argumentMap(), timezone)); + } - /** - * Sets the 'isoWeekYear' of the {@link DateFromParts} to given the fixed value - * - * @param isoWeekYear the fixed value to bind the field - * @return - */ - public IsoWeekDatePartsBuilder isoWeekYear(Number isoWeekYear) { + @Override + protected String getMongoMethod() { + return "$dateFromParts"; + } - this.isoWeekYear = isoWeekYear; - return this; - } + /** + * @author Christoph Strobl + */ + public interface IsoDateFromPartsWithYear { /** - * Sets the 'isoWeekYear' of the {@link DateFromParts} to given the field + * Set the {@literal week date year} to the given value which must resolve to a weekday in range {@code 0 - 9999}. + * Can be a simple value, {@link Field field reference} or {@link AggregationExpression expression}. * - * @param isoWeekYear the field to read the 'isoWeekYear' value from - * @return + * @param isoWeekYear must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal isoWeekYear} is {@literal null}. */ - public IsoWeekDatePartsBuilder isoWeekYearOf(String isoWeekYear) { - - this.isoWeekYear = Fields.field(isoWeekYear); - return this; - } + IsoDateFromParts isoWeekYear(Object isoWeekYear); /** - * Sets the 'isoWeekYear' of the {@link DateFromParts} to given the fixed value + * Set the {@literal week date year} to the value resolved by following the given {@link Field field reference}. * - * @param isoWeekYear the expression to evaluate the 'isoWeekYear' value - * @return + * @param fieldReference must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal fieldReference} is {@literal null}. */ - public IsoWeekDatePartsBuilder isoWeekYearOf(AggregationExpression isoWeekYear) { + default IsoDateFromParts isoWeekYearOf(String fieldReference) { - this.isoWeekYear = isoWeekYear; - return this; + Assert.hasText(fieldReference, "Field reference must not be null nor empty."); + return isoWeekYear(Fields.field(fieldReference)); } /** - * Sets the 'isoWeek' of the {@link DateFromParts} to given the fixed value + * Set the {@literal week date year} to the result of the given {@link AggregationExpression expression}. * - * @param isoWeek the fixed value to bind the field - * @return + * @param expression must not be {@literal null}. + * @return new instance. + * @throws IllegalArgumentException if given {@literal expression} is {@literal null}. */ - public IsoWeekDatePartsBuilder isoWeek(Number isoWeek) { + default IsoDateFromParts isoWeekYearOf(AggregationExpression expression) { - this.isoWeek = isoWeek; - return this; + Assert.notNull(expression, "Expression must not be null!"); + return isoWeekYear(expression); } + } + } - /** - * Sets the 'isoWeek' of the {@link DateFromParts} to given the field - * - * @param isoWeek the field to read the 'isoWeek' value from - * @return - */ - public IsoWeekDatePartsBuilder isoWeekOf(String isoWeek) { - - this.isoWeek = Fields.field(isoWeek); - return this; - } + /** + * {@link AggregationExpression} for {@code $dateToParts}.
+ * NOTE: Requires MongoDB 3.6 or later. + * + * @author Matt Morrissette + * @author Christoph Strobl + * @see https://docs.mongodb.com/manual/reference/operator/aggregation/dateToParts/ + * @since 2.1 + */ + public static class DateToParts extends TimezonedDateAggregationExpression { - /** - * Sets the 'isoWeek' of the {@link DateFromParts} to given the fixed value - * - * @param isoWeek the expression to evaluate the 'isoWeek' value - * @return - */ - public IsoWeekDatePartsBuilder isoWeekOf(AggregationExpression isoWeek) { + private DateToParts(Object value) { + super(value); + } - this.isoWeek = isoWeek; - return this; - } + /** + * Creates new {@link DateToParts}. + * + * @param value must not be {@literal null}. + * @return new instance of {@link DateToParts}. + * @throws IllegalArgumentException if given {@literal value} is {@literal null}. + */ + public static DateToParts dateToParts(Object value) { - /** - * Sets the 'isoDayOfWeek' of the {@link DateFromParts} to given the fixed value - * - * @param isoDayOfWeek the fixed value to bind the field - * @return - */ - public IsoWeekDatePartsBuilder isoDayOfWeek(Number isoDayOfWeek) { + Assert.notNull(value, "Value must not be null!"); + return new DateToParts(Collections.singletonMap("date", value)); + } - this.isoDayOfWeek = isoDayOfWeek; - return this; - } + /** + * Creates new {@link DateToParts}. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link DateToParts}. + * @throws IllegalArgumentException if given {@literal fieldReference} is {@literal null}. + */ + public static DateToParts datePartsOf(String fieldReference) { - /** - * Sets the 'isoDayOfWeek' of the {@link DateFromParts} to given the field - * - * @param isoDayOfWeek the field to read the 'isoDayOfWeek' value from - * @return - */ - public IsoWeekDatePartsBuilder isoDayOfWeekOf(String isoDayOfWeek) { + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return dateToParts(Fields.field(fieldReference)); + } - this.isoDayOfWeek = Fields.field(isoDayOfWeek); - return this; - } + /** + * Creates new {@link DateToParts}. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link DateToParts}. + * @throws IllegalArgumentException if given {@literal expression} is {@literal null}. + */ + public static DateToParts datePartsOf(AggregationExpression expression) { + return dateToParts(expression); + } - /** - * Sets the 'isoDayOfWeek' of the {@link DateFromParts} to given the fixed value - * - * @param isoDayOfWeek the expression to evaluate the 'isoDayOfWeek' value - * @return - */ - public IsoWeekDatePartsBuilder isoDayOfWeekOf(AggregationExpression isoDayOfWeek) { + /** + * Use ISO week date fields in the resulting document. + * + * @return new instance of {@link DateToParts}. + */ + public DateToParts iso8601() { + return new DateToParts(append("iso8601", true)); + } - this.isoDayOfWeek = isoDayOfWeek; - return this; - } + /** + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. + * + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link DateFromParts}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. + */ + @Override + public DateToParts withTimezone(Timezone timezone) { + return new DateToParts(appendTimezone(argumentMap(), timezone)); + } + @Override + protected String getMongoMethod() { + return "$dateToParts"; } + } - private static Map calMap(Object year, Object month, Object day, Object hour, Object minute, - Object second, Object millisecond, Object timezone) { + /** + * {@link AggregationExpression} for {@code $dateFromString}.
+ * NOTE: Requires MongoDB 3.6 or later. + * + * @author Matt Morrissette + * @author Christoph Strobl + * @see https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromString/ + * @since 2.1 + */ + public static class DateFromString extends TimezonedDateAggregationExpression { - final Map vals = new LinkedHashMap<>(11); - put(vals, "year", year, true); - put(vals, "month", month, false); - put(vals, "day", day, false); - putCommonMap(vals, hour, minute, second, millisecond, timezone); - return vals; + private DateFromString(Object value) { + super(value); } - private static Map isoWeekMap(Object isoWeekYear, Object isoWeek, Object isoDayOfWeek, Object hour, - Object minute, Object second, Object millisecond, Object timezone) { - - final Map vals = new LinkedHashMap<>(11); - put(vals, "isoWeekYear", isoWeekYear, true); - put(vals, "isoWeek", isoWeek, false); - put(vals, "isoDayOfWeek", isoDayOfWeek, false); - putCommonMap(vals, hour, minute, second, millisecond, timezone); - return vals; + /** + * Creates new {@link DateFromString}. + * + * @param value must not be {@literal null}. + * @return new instance of {@link DateFromString}. + * @throws IllegalArgumentException if given {@literal value} is {@literal null}. + */ + public static DateFromString fromString(Object value) { + return new DateFromString(Collections.singletonMap("dateString", value)); } - private static void putCommonMap(final Map vals, Object hour, Object minute, Object second, - Object millisecond, Object timezone) { + /** + * Creates new {@link DateFromString}. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link DateFromString}. + * @throws IllegalArgumentException if given {@literal fieldReference} is {@literal null}. + */ + public static DateFromString fromStringOf(String fieldReference) { + return fromString(Fields.field(fieldReference)); + } - put(vals, "hour", hour, false); - put(vals, "minute", minute, false); - put(vals, "second", second, false); - put(vals, "millisecond", millisecond, false); - if (timezone != null) { - Assert.isTrue(DateAggregationExpression.isValidTimezoneObject(timezone), - () -> "Timezone was not a valid timezone: " + timezone - + ". Must be String, AggregationExpression or Field"); - vals.put("timezone", timezone); - } + /** + * Creates new {@link DateFromString}. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link DateFromString}. + * @throws IllegalArgumentException if given {@literal expression} is {@literal null}. + */ + public static DateFromString fromStringOf(AggregationExpression expression) { + return fromString(expression); } - private static void put(final Map map, final String key, final Object val, boolean throwIfAbsent) { + /** + * Optionally set the {@link Timezone} to use. If not specified {@literal UTC} is used.
+ * NOTE: Requires MongoDB 3.6 or later. + * + * @param timezone must not be {@literal null}. Consider {@link Timezone#none()} instead. + * @return new instance of {@link DateFromString}. + * @throws IllegalArgumentException if given {@literal timezone} is {@literal null}. + */ + @Override + public DateFromString withTimezone(Timezone timezone) { + return new DateFromString(appendTimezone(argumentMap(), timezone)); + } - if (val != null) { - map.put(key, val); - } else if (throwIfAbsent) { - throw new IllegalArgumentException(key + "is required"); - } + @Override + protected String getMongoMethod() { + return "$dateFromString"; } } + + @SuppressWarnings("unchecked") + private static T applyTimezone(T instance, Timezone timezone) { + return !ObjectUtils.nullSafeEquals(Timezone.none(), timezone) && !instance.hasTimezone() + ? (T) instance.withTimezone(timezone) : instance; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/DateOperatorsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/DateOperatorsUnitTests.java deleted file mode 100644 index 3054479d9..000000000 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/DateOperatorsUnitTests.java +++ /dev/null @@ -1,899 +0,0 @@ -/* - * Copyright 2016-2018 the original author or authors. - * - * Licensed 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.springframework.data.mongodb.core.aggregation; - -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; -import static org.springframework.data.mongodb.core.aggregation.DateOperators.*; -import static org.springframework.data.mongodb.core.aggregation.LiteralOperators.Literal.*; - -import java.util.Date; - -import org.bson.Document; -import org.junit.Test; - -import com.google.common.collect.Lists; - -/** - * Unit tests for {@link DateOperators}. DATAMONGO-1834 - Add support for aggregation operators $dateFromString, - * $dateFromParts and $dateToParts This test case now covers all existing methods in the DateOperators class as well as - * those added as part of DATAMONGO-1834 - * - * @author Matt Morrissette - */ -public class DateOperatorsUnitTests { - - private static final String FIELD = "field"; - - private static final String VAR = "$"; - - private static final String VAR_FIELD = VAR + FIELD; - - private static final String TIMEZONE = "America/Los_Angeles"; - - private static final String TIMEZONE2 = "America/New_York"; - - private static final String FORMAT = "%Y-%m-%d"; - - private static final Document LITERAL = new Document("$literal", VAR_FIELD); - - private static final String TO_STRING_OP = "$dateToString"; - - private static Object CURRENT_DATE; - - @Test(expected = IllegalArgumentException.class) - public void rejectsEmptyFieldName() { - dateOf(""); - } - - @Test - public void shouldRenderFieldCorrectly() { - - final DateOperatorFactory f = dateOf(FIELD); - assertDateFieldOp(f.dayOfMonth(), "dayOfMonth"); - assertDateFieldOp(f.dayOfWeek(), "dayOfWeek"); - assertDateFieldOp(f.dayOfYear(), "dayOfYear"); - assertDateFieldOp(f.hour(), "hour"); - assertDateFieldOp(f.isoDayOfWeek(), "isoDayOfWeek"); - assertDateFieldOp(f.isoWeek(), "isoWeek"); - assertDateFieldOp(f.isoWeekYear(), "isoWeekYear"); - assertDateFieldOp(f.millisecond(), "millisecond"); - assertDateFieldOp(f.minute(), "minute"); - assertDateFieldOp(f.month(), "month"); - assertDateFieldOp(f.second(), "second"); - assertDateFieldOp(f.week(), "week"); - assertDateFieldOp(f.year(), "year"); - assertQuarterFieldOp(f.quarter()); - assertDateFromStringField(f.fromString()); - assertDateToPartsField(f.toParts(), null); - assertDateToPartsField(f.toIsoWeekParts(), true); - assertDateToPartsField(f.toParts(true), true); - assertDateToPartsField(f.toParts(false), false); - assertDateFieldStringNoTimezoneOp(f.toString(FORMAT)); - assertDateFieldTimezoneOp(f.dayOfMonth(TIMEZONE), "dayOfMonth"); - assertDateFieldTimezoneOp(f.dayOfWeek(TIMEZONE), "dayOfWeek"); - assertDateFieldTimezoneOp(f.dayOfYear(TIMEZONE), "dayOfYear"); - assertDateFieldTimezoneOp(f.hour(TIMEZONE), "hour"); - assertDateFieldTimezoneOp(f.isoDayOfWeek(TIMEZONE), "isoDayOfWeek"); - assertDateFieldTimezoneOp(f.isoWeek(TIMEZONE), "isoWeek"); - assertDateFieldTimezoneOp(f.isoWeekYear(TIMEZONE), "isoWeekYear"); - assertDateFieldTimezoneOp(f.millisecond(TIMEZONE), "millisecond"); - assertDateFieldTimezoneOp(f.minute(TIMEZONE), "minute"); - assertDateFieldTimezoneOp(f.month(TIMEZONE), "month"); - assertDateFieldTimezoneOp(f.second(TIMEZONE), "second"); - assertDateFieldTimezoneOp(f.week(TIMEZONE), "week"); - assertDateFieldTimezoneOp(f.year(TIMEZONE), "year"); - assertDateToPartsFieldTimezone(f.toParts(TIMEZONE), null); - assertDateToPartsFieldTimezone(f.toIsoWeekParts(TIMEZONE), true); - assertDateToPartsFieldTimezone(f.toParts(TIMEZONE, true), true); - assertDateToPartsFieldTimezone(f.toParts(TIMEZONE, false), false); - assertQuarterFieldTimezoneOp(f.quarter(TIMEZONE)); - assertDateFromStringFieldTimezone(f.fromString(TIMEZONE)); - assertDateFieldStringTimezoneOp(f.toString(FORMAT, TIMEZONE)); - } - - @Test - public void shouldRenderFieldTimezoneCorrectly() { - - final DateOperatorFactory f = dateOfWithTimezone(FIELD, TIMEZONE); - assertDateFieldTimezoneOp(f.dayOfMonth(), "dayOfMonth"); - assertDateFieldTimezoneOp(f.dayOfWeek(), "dayOfWeek"); - assertDateFieldTimezoneOp(f.dayOfYear(), "dayOfYear"); - assertDateFieldTimezoneOp(f.hour(), "hour"); - assertDateFieldTimezoneOp(f.isoDayOfWeek(), "isoDayOfWeek"); - assertDateFieldTimezoneOp(f.isoWeek(), "isoWeek"); - assertDateFieldTimezoneOp(f.isoWeekYear(), "isoWeekYear"); - assertDateFieldTimezoneOp(f.millisecond(), "millisecond"); - assertDateFieldTimezoneOp(f.minute(), "minute"); - assertDateFieldTimezoneOp(f.month(), "month"); - assertDateFieldTimezoneOp(f.second(), "second"); - assertDateFieldTimezoneOp(f.week(), "week"); - assertDateFieldTimezoneOp(f.year(), "year"); - assertQuarterFieldTimezoneOp(f.quarter()); - assertDateFromStringFieldTimezone(f.fromString()); - assertDateToPartsFieldTimezone(f.toParts(), null); - assertDateToPartsFieldTimezone(f.toIsoWeekParts(), true); - assertDateToPartsFieldTimezone(f.toParts(true), true); - assertDateToPartsFieldTimezone(f.toParts(false), false); - assertDateFieldStringTimezoneOp(f.toString(FORMAT)); - - assertDateFieldTimezone2Op(f.dayOfMonth(TIMEZONE2), "dayOfMonth"); - assertDateFieldTimezone2Op(f.dayOfWeek(TIMEZONE2), "dayOfWeek"); - assertDateFieldTimezone2Op(f.dayOfYear(TIMEZONE2), "dayOfYear"); - assertDateFieldTimezone2Op(f.hour(TIMEZONE2), "hour"); - assertDateFieldTimezone2Op(f.isoDayOfWeek(TIMEZONE2), "isoDayOfWeek"); - assertDateFieldTimezone2Op(f.isoWeek(TIMEZONE2), "isoWeek"); - assertDateFieldTimezone2Op(f.isoWeekYear(TIMEZONE2), "isoWeekYear"); - assertDateFieldTimezone2Op(f.millisecond(TIMEZONE2), "millisecond"); - assertDateFieldTimezone2Op(f.minute(TIMEZONE2), "minute"); - assertDateFieldTimezone2Op(f.month(TIMEZONE2), "month"); - assertDateFieldTimezone2Op(f.second(TIMEZONE2), "second"); - assertDateFieldTimezone2Op(f.week(TIMEZONE2), "week"); - assertDateFieldTimezone2Op(f.year(TIMEZONE2), "year"); - assertQuarterFieldTimezone2Op(f.quarter(TIMEZONE2)); - assertDateFromStringFieldTimezone2(f.fromString(TIMEZONE2)); - assertDateToPartsFieldTimezone2(f.toParts(TIMEZONE2), null); - assertDateToPartsFieldTimezone2(f.toIsoWeekParts(TIMEZONE2), true); - assertDateToPartsFieldTimezone2(f.toParts(TIMEZONE2, true), true); - assertDateToPartsFieldTimezone2(f.toParts(TIMEZONE2, false), false); - assertDateFieldStringTimezone2Op(f.toString(FORMAT, TIMEZONE2)); - - assertDateFieldOp(f.dayOfMonth(null), "dayOfMonth"); - assertDateFieldOp(f.dayOfWeek(null), "dayOfWeek"); - assertDateFieldOp(f.dayOfYear(null), "dayOfYear"); - assertDateFieldOp(f.hour(null), "hour"); - assertDateFieldOp(f.isoDayOfWeek(null), "isoDayOfWeek"); - assertDateFieldOp(f.isoWeek(null), "isoWeek"); - assertDateFieldOp(f.isoWeekYear(null), "isoWeekYear"); - assertDateFieldOp(f.millisecond(null), "millisecond"); - assertDateFieldOp(f.minute(null), "minute"); - assertDateFieldOp(f.month(null), "month"); - assertDateFieldOp(f.second(null), "second"); - assertDateFieldOp(f.week(null), "week"); - assertDateFieldOp(f.year(null), "year"); - assertQuarterFieldOp(f.quarter(null)); - assertDateFromStringField(f.fromString(null)); - assertDateToPartsField(f.toParts((String) null), null); - assertDateToPartsField(f.toIsoWeekParts(null), true); - assertDateToPartsField(f.toParts(null, true), true); - assertDateToPartsField(f.toParts(null, false), false); - assertDateFieldStringNoTimezoneOp(f.toString(FORMAT, null)); - } - - @Test - public void shouldRenderExprCorrectly() { - - final DateOperatorFactory f = dateOf(asLiteral(VAR_FIELD)); - assertDateExprOp(f.dayOfMonth(), "dayOfMonth"); - assertDateExprOp(f.dayOfWeek(), "dayOfWeek"); - assertDateExprOp(f.dayOfYear(), "dayOfYear"); - assertDateExprOp(f.hour(), "hour"); - assertDateExprOp(f.isoDayOfWeek(), "isoDayOfWeek"); - assertDateExprOp(f.isoWeek(), "isoWeek"); - assertDateExprOp(f.isoWeekYear(), "isoWeekYear"); - assertDateExprOp(f.millisecond(), "millisecond"); - assertDateExprOp(f.minute(), "minute"); - assertDateExprOp(f.month(), "month"); - assertDateExprOp(f.second(), "second"); - assertDateExprOp(f.week(), "week"); - assertDateExprOp(f.year(), "year"); - assertQuarterExprOp(f.quarter()); - assertDateFromStringExpr(f.fromString()); - assertDateToPartsExpr(f.toParts(), null); - assertDateToPartsExpr(f.toIsoWeekParts(), true); - assertDateToPartsExpr(f.toParts(true), true); - assertDateToPartsExpr(f.toParts(false), false); - assertDateExprStringNoTimezoneOp(f.toString(FORMAT)); - assertDateExprTimezoneOp(f.dayOfMonth(TIMEZONE), "dayOfMonth"); - assertDateExprTimezoneOp(f.dayOfWeek(TIMEZONE), "dayOfWeek"); - assertDateExprTimezoneOp(f.dayOfYear(TIMEZONE), "dayOfYear"); - assertDateExprTimezoneOp(f.hour(TIMEZONE), "hour"); - assertDateExprTimezoneOp(f.isoDayOfWeek(TIMEZONE), "isoDayOfWeek"); - assertDateExprTimezoneOp(f.isoWeek(TIMEZONE), "isoWeek"); - assertDateExprTimezoneOp(f.isoWeekYear(TIMEZONE), "isoWeekYear"); - assertDateExprTimezoneOp(f.millisecond(TIMEZONE), "millisecond"); - assertDateExprTimezoneOp(f.minute(TIMEZONE), "minute"); - assertDateExprTimezoneOp(f.month(TIMEZONE), "month"); - assertDateExprTimezoneOp(f.second(TIMEZONE), "second"); - assertDateExprTimezoneOp(f.week(TIMEZONE), "week"); - assertDateExprTimezoneOp(f.year(TIMEZONE), "year"); - assertQuarterExprTimezoneOp(f.quarter(TIMEZONE)); - assertDateFromStringExprTimezone(f.fromString(TIMEZONE)); - assertDateToPartsExprTimezone(f.toParts(TIMEZONE), null); - assertDateToPartsExprTimezone(f.toIsoWeekParts(TIMEZONE), true); - assertDateToPartsExprTimezone(f.toParts(TIMEZONE, true), true); - assertDateToPartsExprTimezone(f.toParts(TIMEZONE, false), false); - assertDateExprStringTimezoneOp(f.toString(FORMAT, TIMEZONE)); - } - - @Test - public void shouldRenderExprTimezoneCorrectly() { - - final DateOperatorFactory f = dateOfWithTimezone(asLiteral(VAR_FIELD), TIMEZONE); - assertDateExprTimezoneOp(f.dayOfMonth(), "dayOfMonth"); - assertDateExprTimezoneOp(f.dayOfWeek(), "dayOfWeek"); - assertDateExprTimezoneOp(f.dayOfYear(), "dayOfYear"); - assertDateExprTimezoneOp(f.hour(), "hour"); - assertDateExprTimezoneOp(f.isoDayOfWeek(), "isoDayOfWeek"); - assertDateExprTimezoneOp(f.isoWeek(), "isoWeek"); - assertDateExprTimezoneOp(f.isoWeekYear(), "isoWeekYear"); - assertDateExprTimezoneOp(f.millisecond(), "millisecond"); - assertDateExprTimezoneOp(f.minute(), "minute"); - assertDateExprTimezoneOp(f.month(), "month"); - assertDateExprTimezoneOp(f.second(), "second"); - assertDateExprTimezoneOp(f.week(), "week"); - assertDateExprTimezoneOp(f.year(), "year"); - assertQuarterExprTimezoneOp(f.quarter()); - assertDateFromStringExprTimezone(f.fromString()); - assertDateToPartsExprTimezone(f.toParts(), null); - assertDateToPartsExprTimezone(f.toIsoWeekParts(), true); - assertDateToPartsExprTimezone(f.toParts(true), true); - assertDateToPartsExprTimezone(f.toParts(false), false); - assertDateExprStringTimezoneOp(f.toString(FORMAT)); - assertDateExprTimezone2Op(f.dayOfMonth(TIMEZONE2), "dayOfMonth"); - assertDateExprTimezone2Op(f.dayOfWeek(TIMEZONE2), "dayOfWeek"); - assertDateExprTimezone2Op(f.dayOfYear(TIMEZONE2), "dayOfYear"); - assertDateExprTimezone2Op(f.hour(TIMEZONE2), "hour"); - assertDateExprTimezone2Op(f.isoDayOfWeek(TIMEZONE2), "isoDayOfWeek"); - assertDateExprTimezone2Op(f.isoWeek(TIMEZONE2), "isoWeek"); - assertDateExprTimezone2Op(f.isoWeekYear(TIMEZONE2), "isoWeekYear"); - assertDateExprTimezone2Op(f.millisecond(TIMEZONE2), "millisecond"); - assertDateExprTimezone2Op(f.minute(TIMEZONE2), "minute"); - assertDateExprTimezone2Op(f.month(TIMEZONE2), "month"); - assertDateExprTimezone2Op(f.second(TIMEZONE2), "second"); - assertDateExprTimezone2Op(f.week(TIMEZONE2), "week"); - assertDateExprTimezone2Op(f.year(TIMEZONE2), "year"); - assertDateToPartsExprTimezone2(f.toParts(TIMEZONE2), null); - assertDateToPartsExprTimezone2(f.toIsoWeekParts(TIMEZONE2), true); - assertDateToPartsExprTimezone2(f.toParts(TIMEZONE2, true), true); - assertDateToPartsExprTimezone2(f.toParts(TIMEZONE2, false), false); - assertDateFromStringExprTimezone2(f.fromString(TIMEZONE2)); - assertQuarterExprStringTimezone2Op(f.quarter(TIMEZONE2)); - assertDateExprTimezone2Op(f.toString(FORMAT, TIMEZONE2)); - assertDateExprOp(f.dayOfMonth(null), "dayOfMonth"); - assertDateExprOp(f.dayOfWeek(null), "dayOfWeek"); - assertDateExprOp(f.dayOfYear(null), "dayOfYear"); - assertDateExprOp(f.hour(null), "hour"); - assertDateExprOp(f.isoDayOfWeek(null), "isoDayOfWeek"); - assertDateExprOp(f.isoWeek(null), "isoWeek"); - assertDateExprOp(f.isoWeekYear(null), "isoWeekYear"); - assertDateExprOp(f.millisecond(null), "millisecond"); - assertDateExprOp(f.minute(null), "minute"); - assertDateExprOp(f.month(null), "month"); - assertDateExprOp(f.second(null), "second"); - assertDateExprOp(f.week(null), "week"); - assertDateExprOp(f.year(null), "year"); - assertQuarterExprOp(f.quarter(null)); - assertDateFromStringExpr(f.fromString(null)); - assertDateToPartsExpr(f.toParts((String) null), null); - assertDateToPartsExpr(f.toIsoWeekParts(null), true); - assertDateToPartsExpr(f.toParts(null, true), true); - assertDateToPartsExpr(f.toParts(null, false), false); - assertDateExprStringNoTimezoneOp(f.toString(FORMAT, null)); - } - - @Test - public void shouldRenderCurrentDateCorrectly() { - - CURRENT_DATE = new Date(); - pShouldRenderCurrentDateCorrectly(DateFactory.fixedDate(CURRENT_DATE)); - } - - private void pShouldRenderCurrentDateCorrectly(DateFactory dateFactory) { - pShouldRenderCurrentDateCorrectly(dateOf(dateFactory).withTimezone(TIMEZONE)); - } - - private void pShouldRenderCurrentDateCorrectly(DateOperatorFactory f) { - - assertCurrentDateTimezoneOp(f.dayOfMonth(), "dayOfMonth"); - assertCurrentDateTimezoneOp(f.dayOfWeek(), "dayOfWeek"); - assertCurrentDateTimezoneOp(f.dayOfYear(), "dayOfYear"); - assertCurrentDateTimezoneOp(f.hour(), "hour"); - assertCurrentDateTimezoneOp(f.isoDayOfWeek(), "isoDayOfWeek"); - assertCurrentDateTimezoneOp(f.isoWeek(), "isoWeek"); - assertCurrentDateTimezoneOp(f.isoWeekYear(), "isoWeekYear"); - assertCurrentDateTimezoneOp(f.millisecond(), "millisecond"); - assertCurrentDateTimezoneOp(f.minute(), "minute"); - assertCurrentDateTimezoneOp(f.month(), "month"); - assertCurrentDateTimezoneOp(f.second(), "second"); - assertCurrentDateTimezoneOp(f.week(), "week"); - assertCurrentDateTimezoneOp(f.year(), "year"); - assertCurrentDateToPartsTimezone(f.toParts(), null); - assertCurrentDateToPartsTimezone(f.toIsoWeekParts(), true); - assertCurrentDateToPartsTimezone(f.toParts(true), true); - assertCurrentDateToPartsTimezone(f.toParts(false), false); - assertQuarterCurrentDateTimezoneOp(f.quarter()); - assertCurrentDateFromStringTimezone(f.fromString()); - assertCurrentDateStringTimezoneOp(f.toString(FORMAT)); - assertCurrentDateTimezone2Op(f.dayOfMonth(TIMEZONE2), "dayOfMonth"); - assertCurrentDateTimezone2Op(f.dayOfWeek(TIMEZONE2), "dayOfWeek"); - assertCurrentDateTimezone2Op(f.dayOfYear(TIMEZONE2), "dayOfYear"); - assertCurrentDateTimezone2Op(f.hour(TIMEZONE2), "hour"); - assertCurrentDateTimezone2Op(f.isoDayOfWeek(TIMEZONE2), "isoDayOfWeek"); - assertCurrentDateTimezone2Op(f.isoWeek(TIMEZONE2), "isoWeek"); - assertCurrentDateTimezone2Op(f.isoWeekYear(TIMEZONE2), "isoWeekYear"); - assertCurrentDateTimezone2Op(f.millisecond(TIMEZONE2), "millisecond"); - assertCurrentDateTimezone2Op(f.minute(TIMEZONE2), "minute"); - assertCurrentDateTimezone2Op(f.month(TIMEZONE2), "month"); - assertCurrentDateTimezone2Op(f.second(TIMEZONE2), "second"); - assertCurrentDateTimezone2Op(f.week(TIMEZONE2), "week"); - assertCurrentDateTimezone2Op(f.year(TIMEZONE2), "year"); - assertCurrentDateToPartsTimezone2(f.toParts(TIMEZONE2), null); - assertCurrentDateToPartsTimezone2(f.toIsoWeekParts(TIMEZONE2), true); - assertCurrentDateToPartsTimezone2(f.toParts(TIMEZONE2, true), true); - assertCurrentDateToPartsTimezone2(f.toParts(TIMEZONE2, false), false); - assertQuarterCurrentDateTimezone2Op(f.quarter(TIMEZONE2)); - assertCurrentDateFromStringTimezone2(f.fromString(TIMEZONE2)); - assertCurrentDateStringTimezone2Op(f.toString(FORMAT, TIMEZONE2)); - assertCurrentDateOp(f.dayOfMonth(null), "dayOfMonth"); - assertCurrentDateOp(f.dayOfWeek(null), "dayOfWeek"); - assertCurrentDateOp(f.dayOfYear(null), "dayOfYear"); - assertCurrentDateOp(f.hour(null), "hour"); - assertCurrentDateOp(f.isoDayOfWeek(null), "isoDayOfWeek"); - assertCurrentDateOp(f.isoWeek(null), "isoWeek"); - assertCurrentDateOp(f.isoWeekYear(null), "isoWeekYear"); - assertCurrentDateOp(f.millisecond(null), "millisecond"); - assertCurrentDateOp(f.minute(null), "minute"); - assertCurrentDateOp(f.month(null), "month"); - assertCurrentDateOp(f.second(null), "second"); - assertCurrentDateOp(f.week(null), "week"); - assertCurrentDateOp(f.year(null), "year"); - assertQuarterCurrentDateOp(f.quarter(null)); - assertCurrentDateFromString(f.fromString(null)); - assertCurrentDateStringNoTimezoneOp(f.toString(FORMAT, null)); - assertCurrentDateToParts(f.toParts((String) null), null); - assertCurrentDateToParts(f.toIsoWeekParts(null), true); - assertCurrentDateToParts(f.toParts(null, true), true); - assertCurrentDateToParts(f.toParts(null, false), false); - } - - @Test - public void shouldRenderCalendarDateFromPartsCorrectly() { - - final Document doc = new Document(); - final Document parts = new Document(); - doc.put("$dateFromParts", parts); - - int year = 2017; - DateFromParts.CalendarDatePartsBuilder dateFromParts = dateFromParts(); - parts.put("year", year); - assertThat(dateFromParts.year(year).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("year", "$year"); - assertThat(dateFromParts.yearOf("year").toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("year", LITERAL); - assertThat(dateFromParts.yearOf(asLiteral(VAR_FIELD)).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("year", year); - dateFromParts.year(year); - - int month = 10; - parts.put("month", month); - assertThat(dateFromParts.month(month).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("month", "$month"); - assertThat(dateFromParts.monthOf("month").toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("month", LITERAL); - assertThat(dateFromParts.monthOf(asLiteral(VAR_FIELD)).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.remove("month"); - dateFromParts.month(null); - assertThat(dateFromParts.toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - - int day = 8; - parts.put("day", day); - assertThat(dateFromParts.day(day).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("day", "$day"); - assertThat(dateFromParts.dayOf("day").toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("day", LITERAL); - assertThat(dateFromParts.dayOf(asLiteral(VAR_FIELD)).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.remove("day"); - dateFromParts.day(null); - assertThat(dateFromParts.toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - - int hour = 9; - parts.put("hour", hour); - assertThat(dateFromParts.hour(hour).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("hour", "$hour"); - assertThat(dateFromParts.hourOf("hour").toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("hour", LITERAL); - assertThat(dateFromParts.hourOf(asLiteral(VAR_FIELD)).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.remove("hour"); - dateFromParts.hour(null); - assertThat(dateFromParts.toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - - int minute = 15; - parts.put("minute", minute); - assertThat(dateFromParts.minute(minute).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("minute", "$minute"); - assertThat(dateFromParts.minuteOf("minute").toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("minute", LITERAL); - assertThat(dateFromParts.minuteOf(asLiteral(VAR_FIELD)).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.remove("minute"); - dateFromParts.minute(null); - assertThat(dateFromParts.toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - - int second = 35; - parts.put("second", second); - assertThat(dateFromParts.second(second).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("second", "$second"); - assertThat(dateFromParts.secondOf("second").toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("second", LITERAL); - assertThat(dateFromParts.secondOf(asLiteral(VAR_FIELD)).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.remove("second"); - dateFromParts.second(null); - assertThat(dateFromParts.toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - - int millisecond = 35; - parts.put("millisecond", millisecond); - assertThat(dateFromParts.millisecond(millisecond).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("millisecond", "$millisecond"); - assertThat(dateFromParts.millisecondOf("millisecond").toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("millisecond", LITERAL); - assertThat(dateFromParts.millisecondOf(asLiteral(VAR_FIELD)).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), - is(doc)); - parts.remove("millisecond"); - dateFromParts.millisecond(null); - assertThat(dateFromParts.toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - - String timezone = "America/New_York"; - parts.put("timezone", timezone); - assertThat(dateFromParts.timezone(timezone).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("timezone", "$timezone"); - assertThat(dateFromParts.timezoneOf("timezone").toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("timezone", LITERAL); - assertThat(dateFromParts.timezoneOf(asLiteral(VAR_FIELD)).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), - is(doc)); - parts.remove("timezone"); - dateFromParts.timezone(null); - assertThat(dateFromParts.toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - } - - @Test - public void shouldRenderIsoWeekDateFromPartsCorrectly() { - - final Document doc = new Document(); - final Document parts = new Document(); - doc.put("$dateFromParts", parts); - - int isoWeekYear = 2017; - DateFromParts.IsoWeekDatePartsBuilder dateFromParts = dateFromIsoWeekParts(); - parts.put("isoWeekYear", isoWeekYear); - assertThat(dateFromParts.isoWeekYear(isoWeekYear).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("isoWeekYear", "$isoWeekYear"); - assertThat(dateFromParts.isoWeekYearOf("isoWeekYear").toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("isoWeekYear", LITERAL); - assertThat(dateFromParts.isoWeekYearOf(asLiteral(VAR_FIELD)).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), - is(doc)); - parts.put("isoWeekYear", isoWeekYear); - dateFromParts.isoWeekYear(isoWeekYear); - - int isoWeek = 25; - parts.put("isoWeek", isoWeek); - assertThat(dateFromParts.isoWeek(isoWeek).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("isoWeek", "$isoWeek"); - assertThat(dateFromParts.isoWeekOf("isoWeek").toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("isoWeek", LITERAL); - assertThat(dateFromParts.isoWeekOf(asLiteral(VAR_FIELD)).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.remove("isoWeek"); - dateFromParts.isoWeek(null); - assertThat(dateFromParts.toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - - int isoDayOfWeek = 4; - parts.put("isoDayOfWeek", isoDayOfWeek); - assertThat(dateFromParts.isoDayOfWeek(isoDayOfWeek).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("isoDayOfWeek", "$isoDayOfWeek"); - assertThat(dateFromParts.isoDayOfWeekOf("isoDayOfWeek").toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("isoDayOfWeek", LITERAL); - assertThat(dateFromParts.isoDayOfWeekOf(asLiteral(VAR_FIELD)).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), - is(doc)); - parts.remove("isoDayOfWeek"); - dateFromParts.isoDayOfWeek(null); - assertThat(dateFromParts.toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - - int hour = 9; - parts.put("hour", hour); - assertThat(dateFromParts.hour(hour).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("hour", "$hour"); - assertThat(dateFromParts.hourOf("hour").toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("hour", LITERAL); - assertThat(dateFromParts.hourOf(asLiteral(VAR_FIELD)).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.remove("hour"); - dateFromParts.hour(null); - assertThat(dateFromParts.toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - - int minute = 15; - parts.put("minute", minute); - assertThat(dateFromParts.minute(minute).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("minute", "$minute"); - assertThat(dateFromParts.minuteOf("minute").toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("minute", LITERAL); - assertThat(dateFromParts.minuteOf(asLiteral(VAR_FIELD)).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.remove("minute"); - dateFromParts.minute(null); - assertThat(dateFromParts.toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - - int second = 35; - parts.put("second", second); - assertThat(dateFromParts.second(second).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("second", "$second"); - assertThat(dateFromParts.secondOf("second").toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("second", LITERAL); - assertThat(dateFromParts.secondOf(asLiteral(VAR_FIELD)).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.remove("second"); - dateFromParts.second(null); - assertThat(dateFromParts.toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - - int millisecond = 35; - parts.put("millisecond", millisecond); - assertThat(dateFromParts.millisecond(millisecond).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("millisecond", "$millisecond"); - assertThat(dateFromParts.millisecondOf("millisecond").toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("millisecond", LITERAL); - assertThat(dateFromParts.millisecondOf(asLiteral(VAR_FIELD)).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), - is(doc)); - parts.remove("millisecond"); - dateFromParts.millisecond(null); - assertThat(dateFromParts.toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - - String timezone = "America/New_York"; - parts.put("timezone", timezone); - assertThat(dateFromParts.timezone(timezone).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("timezone", "$timezone"); - assertThat(dateFromParts.timezoneOf("timezone").toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - parts.put("timezone", LITERAL); - assertThat(dateFromParts.timezoneOf(asLiteral(VAR_FIELD)).toDate().toDocument(Aggregation.DEFAULT_CONTEXT), - is(doc)); - parts.remove("timezone"); - dateFromParts.timezone(null); - assertThat(dateFromParts.toDate().toDocument(Aggregation.DEFAULT_CONTEXT), is(doc)); - } - - @Test(expected = IllegalArgumentException.class) - public void testDateFromPartsNoCalendarYearException() { - dateFromParts().toDate(); - } - - @Test(expected = IllegalArgumentException.class) - public void testDateFromPartsNoIsoWeekYearException() { - dateFromIsoWeekParts().toDate(); - } - - private void assertDateFieldOp(AggregationExpression operation, final String opName) { - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(VAR + opName, VAR_FIELD))); - } - - private void assertDateFieldTimezoneOp(AggregationExpression operation, final String opName) { - - final Document val = new Document(); - val.put("date", VAR_FIELD); - val.put("timezone", TIMEZONE); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(VAR + opName, val))); - } - - private void assertDateFieldTimezone2Op(AggregationExpression operation, final String opName) { - - final Document val = new Document(); - val.put("date", VAR_FIELD); - val.put("timezone", TIMEZONE2); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(VAR + opName, val))); - } - - private void assertDateExprOp(AggregationExpression operation, final String opName) { - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(VAR + opName, LITERAL))); - } - - private void assertDateExprTimezoneOp(AggregationExpression operation, final String opName) { - - final Document val = new Document(); - val.put("date", LITERAL); - val.put("timezone", TIMEZONE); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(VAR + opName, val))); - } - - private void assertDateExprTimezone2Op(AggregationExpression operation, final String opName) { - - final Document val = new Document(); - val.put("date", LITERAL); - val.put("timezone", TIMEZONE2); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(VAR + opName, val))); - } - - private void assertDateFieldStringNoTimezoneOp(AggregationExpression operation) { - - final Document val = new Document(); - val.put("date", VAR_FIELD); - val.put("format", FORMAT); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(TO_STRING_OP, val))); - } - - private void assertDateFieldStringTimezoneOp(AggregationExpression operation) { - - final Document val = new Document(); - val.put("date", VAR_FIELD); - val.put("format", FORMAT); - val.put("timezone", TIMEZONE); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(TO_STRING_OP, val))); - } - - private void assertDateFieldStringTimezone2Op(AggregationExpression operation) { - - final Document val = new Document(); - val.put("date", VAR_FIELD); - val.put("format", FORMAT); - val.put("timezone", TIMEZONE2); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(TO_STRING_OP, val))); - } - - private void assertDateExprStringNoTimezoneOp(AggregationExpression operation) { - - final Document val = new Document(); - val.put("date", LITERAL); - val.put("format", FORMAT); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(TO_STRING_OP, val))); - } - - private void assertDateExprStringTimezoneOp(AggregationExpression operation) { - final Document val = new Document(); - val.put("date", LITERAL); - val.put("format", FORMAT); - val.put("timezone", TIMEZONE); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(TO_STRING_OP, val))); - } - - private void assertDateExprTimezone2Op(AggregationExpression operation) { - - final Document val = new Document(); - val.put("date", LITERAL); - val.put("format", FORMAT); - val.put("timezone", TIMEZONE2); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(TO_STRING_OP, val))); - } - - private void assertDateFromStringField(AggregationExpression operation) { - - final Document val = new Document(); - val.put("dateString", VAR_FIELD); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document("$dateFromString", val))); - } - - private void assertDateFromStringFieldTimezone(AggregationExpression operation) { - - final Document val = new Document(); - val.put("dateString", VAR_FIELD); - val.put("timezone", TIMEZONE); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document("$dateFromString", val))); - } - - private void assertDateFromStringFieldTimezone2(AggregationExpression operation) { - - final Document val = new Document(); - val.put("dateString", VAR_FIELD); - val.put("timezone", TIMEZONE2); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document("$dateFromString", val))); - } - - private void assertDateFromStringExpr(AggregationExpression operation) { - - final Document val = new Document(); - val.put("dateString", LITERAL); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document("$dateFromString", val))); - } - - private void assertDateFromStringExprTimezone(AggregationExpression operation) { - - final Document val = new Document(); - val.put("dateString", LITERAL); - val.put("timezone", TIMEZONE); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document("$dateFromString", val))); - } - - private void assertDateFromStringExprTimezone2(AggregationExpression operation) { - - final Document val = new Document(); - val.put("dateString", LITERAL); - val.put("timezone", TIMEZONE2); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document("$dateFromString", val))); - } - - private void assertCurrentDateFromString(AggregationExpression operation) { - - final Document val = new Document(); - val.put("dateString", CURRENT_DATE); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document("$dateFromString", val))); - } - - private void assertCurrentDateFromStringTimezone(AggregationExpression operation) { - - final Document val = new Document(); - val.put("dateString", CURRENT_DATE); - val.put("timezone", TIMEZONE); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document("$dateFromString", val))); - } - - private void assertCurrentDateFromStringTimezone2(AggregationExpression operation) { - - final Document val = new Document(); - val.put("dateString", CURRENT_DATE); - val.put("timezone", TIMEZONE2); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document("$dateFromString", val))); - } - - private void assertDateToParts(AggregationExpression operation, Boolean iso8601, Object dateValue, - Object timezoneValue) { - - final Document val = new Document(); - val.put("date", dateValue); - if (iso8601 != null) { - val.put("iso8601", iso8601); - } - if (timezoneValue != null) { - val.put("timezone", timezoneValue); - } - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document("$dateToParts", val))); - } - - private void assertDateToPartsField(AggregationExpression operation, Boolean iso8601) { - assertDateToParts(operation, iso8601, VAR_FIELD, null); - } - - private void assertDateToPartsFieldTimezone(AggregationExpression operation, Boolean iso8601) { - assertDateToParts(operation, iso8601, VAR_FIELD, TIMEZONE); - } - - private void assertDateToPartsFieldTimezone2(AggregationExpression operation, Boolean iso8601) { - assertDateToParts(operation, iso8601, VAR_FIELD, TIMEZONE2); - } - - private void assertDateToPartsExpr(AggregationExpression operation, Boolean iso8601) { - assertDateToParts(operation, iso8601, LITERAL, null); - } - - private void assertDateToPartsExprTimezone(AggregationExpression operation, Boolean iso8601) { - assertDateToParts(operation, iso8601, LITERAL, TIMEZONE); - } - - private void assertDateToPartsExprTimezone2(AggregationExpression operation, Boolean iso8601) { - assertDateToParts(operation, iso8601, LITERAL, TIMEZONE2); - } - - private void assertCurrentDateToParts(AggregationExpression operation, Boolean iso8601) { - assertDateToParts(operation, iso8601, CURRENT_DATE, null); - } - - private void assertCurrentDateToPartsTimezone(AggregationExpression operation, Boolean iso8601) { - assertDateToParts(operation, iso8601, CURRENT_DATE, TIMEZONE); - } - - private void assertCurrentDateToPartsTimezone2(AggregationExpression operation, Boolean iso8601) { - assertDateToParts(operation, iso8601, CURRENT_DATE, TIMEZONE2); - } - - private void assertQuarter(AggregationExpression operation, final Document monthDoc) { - final Document document = new Document("$cond", - new Document() - .append("if", - new Document("$lte", - Lists.newArrayList(monthDoc, - 3))) - .append("then", 1).append("else", - new Document("$cond", - new Document().append("if", new Document("$lte", Lists.newArrayList(monthDoc, 6))).append("then", 2) - .append("else", - new Document("$cond", - new Document().append("if", new Document("$lte", Lists.newArrayList(monthDoc, 9))) - .append("then", 3).append("else", 4)))))); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(document)); - } - - private void assertQuarterFieldOp(AggregationExpression operation) { - assertQuarter(operation, new Document("$month", VAR_FIELD)); - } - - private void assertQuarterFieldTimezoneOp(AggregationExpression operation) { - final Document val = new Document(); - val.put("date", VAR_FIELD); - val.put("timezone", TIMEZONE); - assertQuarter(operation, new Document("$month", val)); - } - - private void assertQuarterFieldTimezone2Op(AggregationExpression operation) { - final Document val = new Document(); - val.put("date", VAR_FIELD); - val.put("timezone", TIMEZONE2); - assertQuarter(operation, new Document("$month", val)); - } - - private void assertQuarterExprOp(AggregationExpression operation) { - assertQuarter(operation, new Document("$month", LITERAL)); - } - - private void assertQuarterExprTimezoneOp(AggregationExpression operation) { - final Document val = new Document(); - val.put("date", LITERAL); - val.put("timezone", TIMEZONE); - assertQuarter(operation, new Document("$month", val)); - } - - private void assertQuarterExprStringTimezone2Op(AggregationExpression operation) { - final Document val = new Document(); - val.put("date", LITERAL); - val.put("timezone", TIMEZONE2); - assertQuarter(operation, new Document("$month", val)); - } - - private void assertQuarterCurrentDateOp(AggregationExpression operation) { - assertQuarter(operation, new Document("$month", CURRENT_DATE)); - } - - private void assertQuarterCurrentDateTimezoneOp(AggregationExpression operation) { - final Document val = new Document(); - val.put("date", CURRENT_DATE); - val.put("timezone", TIMEZONE); - assertQuarter(operation, new Document("$month", val)); - } - - private void assertQuarterCurrentDateTimezone2Op(AggregationExpression operation) { - final Document val = new Document(); - val.put("date", CURRENT_DATE); - val.put("timezone", TIMEZONE2); - assertQuarter(operation, new Document("$month", val)); - } - - private void assertCurrentDateOp(AggregationExpression operation, final String opName) { - - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(VAR + opName, CURRENT_DATE))); - } - - private void assertCurrentDateTimezoneOp(AggregationExpression operation, final String opName) { - - final Document val = new Document(); - val.put("date", CURRENT_DATE); - val.put("timezone", TIMEZONE); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(VAR + opName, val))); - } - - private void assertCurrentDateTimezone2Op(AggregationExpression operation, final String opName) { - - final Document val = new Document(); - val.put("date", CURRENT_DATE); - val.put("timezone", TIMEZONE2); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(VAR + opName, val))); - } - - private void assertCurrentDateStringNoTimezoneOp(AggregationExpression operation) { - - final Document val = new Document(); - val.put("date", CURRENT_DATE); - val.put("format", FORMAT); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(TO_STRING_OP, val))); - } - - private void assertCurrentDateStringTimezoneOp(AggregationExpression operation) { - - final Document val = new Document(); - val.put("date", CURRENT_DATE); - val.put("format", FORMAT); - val.put("timezone", TIMEZONE); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(TO_STRING_OP, val))); - } - - private void assertCurrentDateStringTimezone2Op(AggregationExpression operation) { - - final Document val = new Document(); - val.put("date", CURRENT_DATE); - val.put("format", FORMAT); - val.put("timezone", TIMEZONE2); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT), is(new Document(TO_STRING_OP, val))); - } - -} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java index dec24890c..f246090ab 100755 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java @@ -35,6 +35,7 @@ import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Reduce.P import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Reduce.Variable; import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Slice; import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Switch.CaseOperator; +import org.springframework.data.mongodb.core.aggregation.DateOperators.Timezone; import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder; import org.springframework.data.mongodb.core.aggregation.StringOperators.Concat; import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; @@ -1083,6 +1084,17 @@ public class ProjectionOperationUnitTests { assertThat(agg).isEqualTo(Document.parse("{ $project: { dayOfYear: { $dayOfYear: \"$date\" } } }")); } + @Test // DATAMONGO-1834 + public void shouldRenderDayOfYearAggregationExpressionWithTimezone() { + + Document agg = project() + .and(DateOperators.dateOf("date").withTimezone(Timezone.valueOf("America/Chicago")).dayOfYear()).as("dayOfYear") + .toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse( + "{ $project: { dayOfYear: { $dayOfYear: { \"date\" : \"$date\", \"timezone\" : \"America/Chicago\" } } } }")); + } + @Test // DATAMONGO-1536 public void shouldRenderDayOfMonthAggregationExpression() { @@ -1092,6 +1104,17 @@ public class ProjectionOperationUnitTests { assertThat(agg).isEqualTo(Document.parse("{ $project: { day: { $dayOfMonth: \"$date\" }} }")); } + @Test // DATAMONGO-1834 + public void shouldRenderDayOfMonthAggregationExpressionWithTimezone() { + + Document agg = project() + .and(DateOperators.dateOf("date").withTimezone(Timezone.valueOf("America/Chicago")).dayOfMonth()).as("day") + .toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse( + "{ $project: { day: { $dayOfMonth: { \"date\" : \"$date\", \"timezone\" : \"America/Chicago\" } } } } }")); + } + @Test // DATAMONGO-1536 public void shouldRenderDayOfWeekAggregationExpression() { @@ -1101,6 +1124,17 @@ public class ProjectionOperationUnitTests { assertThat(agg).isEqualTo(Document.parse("{ $project: { dayOfWeek: { $dayOfWeek: \"$date\" } } }")); } + @Test // DATAMONGO-1834 + public void shouldRenderDayOfWeekAggregationExpressionWithTimezone() { + + Document agg = project() + .and(DateOperators.dateOf("date").withTimezone(Timezone.valueOf("America/Chicago")).dayOfWeek()).as("dayOfWeek") + .toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse( + "{ $project: { dayOfWeek: { $dayOfWeek: { \"date\" : \"$date\", \"timezone\" : \"America/Chicago\" } } } } }")); + } + @Test // DATAMONGO-1536 public void shouldRenderYearAggregationExpression() { @@ -1110,6 +1144,16 @@ public class ProjectionOperationUnitTests { assertThat(agg).isEqualTo(Document.parse("{ $project: { year: { $year: \"$date\" } } }")); } + @Test // DATAMONGO-1834 + public void shouldRenderYearAggregationExpressionWithTimezone() { + + Document agg = project().and(DateOperators.dateOf("date").withTimezone(Timezone.valueOf("America/Chicago")).year()) + .as("year").toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document + .parse("{ $project: { year: { $year: { \"date\" : \"$date\", \"timezone\" : \"America/Chicago\" } } } } }")); + } + @Test // DATAMONGO-1536 public void shouldRenderMonthAggregationExpression() { @@ -1119,6 +1163,16 @@ public class ProjectionOperationUnitTests { assertThat(agg).isEqualTo(Document.parse("{ $project: { month: { $month: \"$date\" } } }")); } + @Test // DATAMONGO-1834 + public void shouldRenderMonthAggregationExpressionWithTimezone() { + + Document agg = project().and(DateOperators.dateOf("date").withTimezone(Timezone.valueOf("America/Chicago")).month()) + .as("month").toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document + .parse("{ $project: { month: { $month: { \"date\" : \"$date\", \"timezone\" : \"America/Chicago\" } } } } }")); + } + @Test // DATAMONGO-1536 public void shouldRenderWeekAggregationExpression() { @@ -1128,6 +1182,16 @@ public class ProjectionOperationUnitTests { assertThat(agg).isEqualTo(Document.parse("{ $project: { week: { $week: \"$date\" } } }")); } + @Test // DATAMONGO-1834 + public void shouldRenderWeekAggregationExpressionWithTimezone() { + + Document agg = project().and(DateOperators.dateOf("date").withTimezone(Timezone.valueOf("America/Chicago")).week()) + .as("week").toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document + .parse("{ $project: { week: { $week: { \"date\" : \"$date\", \"timezone\" : \"America/Chicago\" } } } } }")); + } + @Test // DATAMONGO-1536 public void shouldRenderHourAggregationExpression() { @@ -1137,6 +1201,16 @@ public class ProjectionOperationUnitTests { assertThat(agg).isEqualTo(Document.parse("{ $project: { hour: { $hour: \"$date\" } } }")); } + @Test // DATAMONGO-1834 + public void shouldRenderHourAggregationExpressionWithTimezone() { + + Document agg = project().and(DateOperators.dateOf("date").withTimezone(Timezone.valueOf("America/Chicago")).hour()) + .as("hour").toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document + .parse("{ $project: { hour: { $hour: { \"date\" : \"$date\", \"timezone\" : \"America/Chicago\" } } } } }")); + } + @Test // DATAMONGO-1536 public void shouldRenderMinuteAggregationExpression() { @@ -1146,6 +1220,17 @@ public class ProjectionOperationUnitTests { assertThat(agg).isEqualTo(Document.parse("{ $project: { minute: { $minute: \"$date\" } } }")); } + @Test // DATAMONGO-1834 + public void shouldRenderMinuteAggregationExpressionWithTimezone() { + + Document agg = project() + .and(DateOperators.dateOf("date").withTimezone(Timezone.valueOf("America/Chicago")).minute()).as("minute") + .toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse( + "{ $project: { minute: { $minute: { \"date\" : \"$date\", \"timezone\" : \"America/Chicago\" } } } } }")); + } + @Test // DATAMONGO-1536 public void shouldRenderSecondAggregationExpression() { @@ -1155,6 +1240,17 @@ public class ProjectionOperationUnitTests { assertThat(agg).isEqualTo(Document.parse("{ $project: { second: { $second: \"$date\" } } }")); } + @Test // DATAMONGO-1834 + public void shouldRenderSecondAggregationExpressionWithTimezone() { + + Document agg = project() + .and(DateOperators.dateOf("date").withTimezone(Timezone.valueOf("America/Chicago")).second()).as("second") + .toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse( + "{ $project: { second: { $second: { \"date\" : \"$date\", \"timezone\" : \"America/Chicago\" } } } } }")); + } + @Test // DATAMONGO-1536 public void shouldRenderMillisecondAggregationExpression() { @@ -1164,6 +1260,17 @@ public class ProjectionOperationUnitTests { assertThat(agg).isEqualTo(Document.parse("{ $project: { msec: { $millisecond: \"$date\" } } }")); } + @Test // DATAMONGO-1834 + public void shouldRenderMillisecondAggregationExpressionWithTimezone() { + + Document agg = project() + .and(DateOperators.dateOf("date").withTimezone(Timezone.valueOf("America/Chicago")).millisecond()).as("msec") + .toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse( + "{ $project: { msec: { $millisecond: { \"date\" : \"$date\", \"timezone\" : \"America/Chicago\" } } } } }")); + } + @Test // DATAMONGO-1536 public void shouldRenderDateToString() { @@ -1184,6 +1291,17 @@ public class ProjectionOperationUnitTests { Document.parse("{ $project: { time: { $dateToString: { format: \"%H:%M:%S:%L\", date: \"$date\" } } } }")); } + @Test // DATAMONGO-1834 + public void shouldRenderDateToStringAggregationExpressionWithTimezone() { + + Document agg = project() + .and(DateOperators.dateOf("date").withTimezone(Timezone.valueOf("America/Chicago")).toString("%H:%M:%S:%L")) + .as("time").toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse( + "{ $project: { time: { $dateToString: { format: \"%H:%M:%S:%L\", date: \"$date\", \"timezone\" : \"America/Chicago\" } } } } } }")); + } + @Test // DATAMONGO-1536 public void shouldRenderSumAggregationExpression() { @@ -1440,11 +1558,11 @@ public class ProjectionOperationUnitTests { .as("finalTotal").toDocument(Aggregation.DEFAULT_CONTEXT); assertThat(agg).isEqualTo(Document.parse("{ $project:{ \"finalTotal\" : { \"$let\": {" + // - "\"vars\": {" + // - "\"total\": { \"$add\": [ \"$price\", \"$tax\" ] }," + // - "\"discounted\": { \"$cond\": { \"if\": \"$applyDiscount\", \"then\": 0.9, \"else\": 1.0 } }" + // - "}," + // - "\"in\": { \"$multiply\": [ \"$$total\", \"$$discounted\" ] }" + // + "\"vars\": {" + // + "\"total\": { \"$add\": [ \"$price\", \"$tax\" ] }," + // + "\"discounted\": { \"$cond\": { \"if\": \"$applyDiscount\", \"then\": 0.9, \"else\": 1.0 } }" + // + "}," + // + "\"in\": { \"$multiply\": [ \"$$total\", \"$$discounted\" ] }" + // "}}}}")); } @@ -1463,11 +1581,11 @@ public class ProjectionOperationUnitTests { .as("finalTotal").toDocument(Aggregation.DEFAULT_CONTEXT); assertThat(agg).isEqualTo(Document.parse("{ $project:{ \"finalTotal\" : { \"$let\": {" + // - "\"vars\": {" + // - "\"total\": { \"$add\": [ \"$price\", \"$tax\" ] }," + // - "\"discounted\": { \"$cond\": { \"if\": \"$applyDiscount\", \"then\": 0.9, \"else\": 1.0 } }" + // - "}," + // - "\"in\": { \"$multiply\": [ \"$$total\", \"$$discounted\" ] }" + // + "\"vars\": {" + // + "\"total\": { \"$add\": [ \"$price\", \"$tax\" ] }," + // + "\"discounted\": { \"$cond\": { \"if\": \"$applyDiscount\", \"then\": 0.9, \"else\": 1.0 } }" + // + "}," + // + "\"in\": { \"$multiply\": [ \"$$total\", \"$$discounted\" ] }" + // "}}}}")); } @@ -1660,6 +1778,17 @@ public class ProjectionOperationUnitTests { assertThat(agg).isEqualTo(Document.parse("{ $project : { dayOfWeek: { $isoDayOfWeek: \"$birthday\" } } }")); } + @Test // DATAMONGO-1834 + public void shouldRenderIsoDayOfWeekWithTimezoneCorrectly() { + + Document agg = project() + .and(DateOperators.dateOf("birthday").withTimezone(Timezone.valueOf("America/Chicago")).isoDayOfWeek()) + .as("dayOfWeek").toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse( + "{ $project : { dayOfWeek: { $isoDayOfWeek: { \"date\" : \"$birthday\", \"timezone\" : \"America/Chicago\" } } } } }")); + } + @Test // DATAMONGO-1548 public void shouldRenderIsoWeekCorrectly() { @@ -1669,6 +1798,17 @@ public class ProjectionOperationUnitTests { assertThat(agg).isEqualTo(Document.parse("{ $project : { weekNumber: { $isoWeek: \"$date\" } } }")); } + @Test // DATAMONGO-1834 + public void shouldRenderIsoWeekWithTimezoneCorrectly() { + + Document agg = project() + .and(DateOperators.dateOf("date").withTimezone(Timezone.valueOf("America/Chicago")).isoWeek()).as("weekNumber") + .toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse( + "{ $project : { weekNumber: { $isoWeek: { \"date\" : \"$date\", \"timezone\" : \"America/Chicago\" } } } } }")); + } + @Test // DATAMONGO-1548 public void shouldRenderIsoWeekYearCorrectly() { @@ -1678,6 +1818,17 @@ public class ProjectionOperationUnitTests { assertThat(agg).isEqualTo(Document.parse("{ $project : { yearNumber: { $isoWeekYear: \"$date\" } } }")); } + @Test // DATAMONGO-1834 + public void shouldRenderIsoWeekYearWithTimezoneCorrectly() { + + Document agg = project() + .and(DateOperators.dateOf("date").withTimezone(Timezone.valueOf("America/Chicago")).isoWeekYear()) + .as("yearNumber").toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse( + "{ $project : { yearNumber: { $isoWeekYear: { \"date\" : \"$date\", \"timezone\" : \"America/Chicago\" } } } } }")); + } + @Test // DATAMONGO-1548 public void shouldRenderSwitchCorrectly() { @@ -1728,6 +1879,128 @@ public class ProjectionOperationUnitTests { assertThat(agg).isEqualTo(Document.parse("{ $project : { a: { $type: \"$a\" } } }")); } + @Test // DATAMONGO-1834 + public void shouldRenderDateFromPartsWithJustTheYear() { + + Document agg = project().and(DateOperators.dateFromParts().year(2018)).as("newDate") + .toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse("{ $project : { newDate: { $dateFromParts: { year : 2018 } } } }")); + } + + @Test // DATAMONGO-1834 + public void shouldRenderDateFromParts() { + + Document agg = project() + .and(DateOperators.dateFromParts().year(2018).month(3).day(23).hour(14).minute(25).second(10).milliseconds(2)) + .as("newDate").toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse( + "{ $project : { newDate: { $dateFromParts: { year : 2018, month : 3, day : 23, hour : 14, minute : 25, second : 10, milliseconds : 2 } } } }")); + } + + @Test // DATAMONGO-1834 + public void shouldRenderDateFromPartsWithTimezone() { + + Document agg = project() + .and(DateOperators.dateFromParts().withTimezone(Timezone.valueOf("America/Chicago")).year(2018)).as("newDate") + .toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document + .parse("{ $project : { newDate: { $dateFromParts: { year : 2018, timezone : \"America/Chicago\" } } } }")); + } + + @Test // DATAMONGO-1834 + public void shouldRenderIsoDateFromPartsWithJustTheYear() { + + Document agg = project().and(DateOperators.dateFromParts().isoWeekYear(2018)).as("newDate") + .toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse("{ $project : { newDate: { $dateFromParts: { isoWeekYear : 2018 } } } }")); + } + + @Test // DATAMONGO-1834 + public void shouldRenderIsoDateFromParts() { + + Document agg = project().and(DateOperators.dateFromParts().isoWeekYear(2018).isoWeek(12).isoDayOfWeek(5).hour(14) + .minute(30).second(42).milliseconds(2)).as("newDate").toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse( + "{ $project : { newDate: { $dateFromParts: { isoWeekYear : 2018, isoWeek : 12, isoDayOfWeek : 5, hour : 14, minute : 30, second : 42, milliseconds : 2 } } } }")); + } + + @Test // DATAMONGO-1834 + public void shouldRenderIsoDateFromPartsWithTimezone() { + + Document agg = project() + .and(DateOperators.dateFromParts().withTimezone(Timezone.valueOf("America/Chicago")).isoWeekYear(2018)) + .as("newDate").toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse( + "{ $project : { newDate: { $dateFromParts: { isoWeekYear : 2018, timezone : \"America/Chicago\" } } } }")); + } + + @Test // DATAMONGO-1834 + public void shouldRenderDateToParts() { + + Document agg = project().and(DateOperators.dateOf("date").toParts()).as("newDate") + .toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse("{ $project : { newDate: { $dateToParts: { date : \"$date\" } } } }")); + } + + @Test // DATAMONGO-1834 + public void shouldRenderDateToIsoParts() { + + Document agg = project().and(DateOperators.dateOf("date").toParts().iso8601()).as("newDate") + .toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo( + Document.parse("{ $project : { newDate: { $dateToParts: { date : \"$date\", iso8601 : true } } } }")); + } + + @Test // DATAMONGO-1834 + public void shouldRenderDateToPartsWithTimezone() { + + Document agg = project() + .and(DateOperators.dateOf("date").withTimezone(Timezone.valueOf("America/Chicago")).toParts()).as("newDate") + .toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document + .parse("{ $project : { newDate: { $dateToParts: { date : \"$date\", timezone : \"America/Chicago\" } } } }")); + } + + @Test // DATAMONGO-1834 + public void shouldRenderDateFromString() { + + Document agg = project().and(DateOperators.dateFromString("2017-02-08T12:10:40.787")).as("newDate") + .toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document + .parse("{ $project : { newDate: { $dateFromString: { dateString : \"2017-02-08T12:10:40.787\" } } } }")); + } + + @Test // DATAMONGO-1834 + public void shouldRenderDateFromStringWithFieldReference() { + + Document agg = project().and(DateOperators.dateOf("date").fromString()).as("newDate") + .toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg) + .isEqualTo(Document.parse("{ $project : { newDate: { $dateFromString: { dateString : \"$date\" } } } }")); + } + + @Test // DATAMONGO-1834 + public void shouldRenderDateFromStringWithTimezone() { + + Document agg = project() + .and(DateOperators.dateFromString("2017-02-08T12:10:40.787").withTimezone(Timezone.valueOf("America/Chicago"))) + .as("newDate").toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg).isEqualTo(Document.parse( + "{ $project : { newDate: { $dateFromString: { dateString : \"2017-02-08T12:10:40.787\", timezone : \"America/Chicago\" } } } }")); + } + private static Document exctractOperation(String field, Document fromProjectClause) { return (Document) fromProjectClause.get(field); } diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index 1508e6050..ed4241552 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -2051,7 +2051,7 @@ At the time of this writing we provide support for the following Aggregation Ope | literal | Date Aggregation Operators -| dayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateToString, isoDayOfWeek, isoWeek, isoWeekYear +| dayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateToString, dateFromString, dateFromParts, dateToParts, isoDayOfWeek, isoWeek, isoWeekYear | Variable Operators | map