From 281c7a3993f03772703d18083894999af4836457 Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Thu, 17 Jul 2025 14:28:43 -0400 Subject: [PATCH 1/5] make format optional and create testing case about $dateToString aggregation with timezone outputs invalid ISO8601 string --- .../com/mongodb/client/model/mql/MqlDate.java | 14 +++++++++ .../client/model/mql/MqlExpression.java | 23 +++++++++++--- .../mql/TypeMqlValuesFunctionalTest.java | 30 +++++++++++++++++++ 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlDate.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlDate.java index b6600aaf689..63637df6883 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlDate.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlDate.java @@ -138,6 +138,20 @@ public interface MqlDate extends MqlValue { */ MqlString asString(MqlString timezone, MqlString format); + /** + * The string representation of {@code this} date as determined by the + * provided {@code timezone} with default format as below: + * + * + * + * @param timezone the UTC Offset or Olson Timezone Identifier. + * @return the resulting value. + */ + MqlString asString(MqlString timezone); + /** * The result of passing {@code this} value to the provided function. * Equivalent to {@code f.apply(this)}, and allows lambdas and static, diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java index eb7ea9a68cd..f6527aa2a50 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java @@ -17,6 +17,7 @@ package com.mongodb.client.model.mql; import com.mongodb.assertions.Assertions; +import com.mongodb.lang.Nullable; import org.bson.BsonArray; import org.bson.BsonDocument; import org.bson.BsonInt32; @@ -947,10 +948,24 @@ public MqlInteger millisecond(final MqlString timezone) { public MqlString asString(final MqlString timezone, final MqlString format) { Assertions.notNull("timezone", timezone); Assertions.notNull("format", format); - return newMqlExpression((cr) -> astDoc("$dateToString", new BsonDocument() - .append("date", this.toBsonValue(cr)) - .append("format", toBsonValue(cr, format)) - .append("timezone", toBsonValue(cr, timezone)))); + return doAsString(timezone, format); + } + + @Override + public MqlString asString(final MqlString timezone) { + Assertions.notNull("timezone", timezone); + return doAsString(timezone, null); + } + + private MqlString doAsString(final MqlString timezone, @Nullable final MqlString format) { + return newMqlExpression((cr) -> { + BsonDocument document = new BsonDocument("date", this.toBsonValue(cr)); + if (format != null) { + document.append("format", toBsonValue(cr, format)); + } + document.append("timezone", toBsonValue(cr, timezone)); + return astDoc("$dateToString", document); + }); } @Override diff --git a/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java index 9083001cf99..60467d6127d 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java @@ -33,6 +33,7 @@ import static com.mongodb.client.model.mql.MqlValues.ofIntegerArray; import static com.mongodb.client.model.mql.MqlValues.ofMap; import static com.mongodb.client.model.mql.MqlValues.ofNull; +import static java.time.format.DateTimeFormatter.ISO_INSTANT; import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -225,6 +226,35 @@ public void dateAsStringTest() { of(instant).asString(of("America/New_York"), of("%Y-%m-%dT%H:%M:%S.%L%z"))); } + /** + * Tests that with default format and non-UTC timezone, {@code $dateToString} won't output invalid ISO 8601 string ending with 'Z'. + * Ticket: JAVA-5044 + */ + @Test + void dateAsStringWithDefaultFormat() { + // the bug was reported on v6.0.3, but was fixed in v7.1 without backporting + // due to the concern that it is a breaking change. + assumeTrue(serverVersionAtLeast(7, 1)); + + final Instant instant = Instant.parse("2025-07-17T18:00:36.052Z"); + MqlDate date = of(instant); + ZonedDateTime utcDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of(ZoneOffset.UTC.getId())); + + // UTC timezone's default format should be "%Y-%m-%dT%H:%M:%S.%LZ" + assertExpression( + utcDateTime.format(ISO_INSTANT), + date.asString(of("UTC")), + "{'$dateToString': {'date': {'$date': '2025-07-17T18:00:36.052Z'}, " + + "'timezone': 'UTC'}}"); + + // non-UTC timezone's default format should be "%Y-%m-%dT%H:%M:%S.%L" + assertExpression( + utcDateTime.withZoneSameInstant(ZoneId.of("America/New_York")).format(ISO_LOCAL_DATE_TIME), + date.asString(of("America/New_York")), + "{'$dateToString': {'date': {'$date': '2025-07-17T18:00:36.052Z'}, " + + "'timezone': 'America/New_York'}}"); + } + // parse string @Test From 9b6b7a3ef890d1c61e0426df6c40c5ba0b5f3f9b Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Thu, 17 Jul 2025 17:53:35 -0400 Subject: [PATCH 2/5] Update driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java Co-authored-by: Viacheslav Babanin --- .../mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java index 60467d6127d..575fd0145dd 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java @@ -227,7 +227,7 @@ public void dateAsStringTest() { } /** - * Tests that with default format and non-UTC timezone, {@code $dateToString} won't output invalid ISO 8601 string ending with 'Z'. + * Tests that with server default format and non-UTC timezone, {@code $dateToString} won't output invalid ISO 8601 string ending with 'Z'. * Ticket: JAVA-5044 */ @Test From f4f59bd342dbbfb20c476fce7fe3252073e0cac8 Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Thu, 17 Jul 2025 17:57:15 -0400 Subject: [PATCH 3/5] code change as per review comments --- .../client/model/mql/MqlExpression.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java index f6527aa2a50..765926b0a08 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java @@ -17,7 +17,6 @@ package com.mongodb.client.model.mql; import com.mongodb.assertions.Assertions; -import com.mongodb.lang.Nullable; import org.bson.BsonArray; import org.bson.BsonDocument; import org.bson.BsonInt32; @@ -948,25 +947,20 @@ public MqlInteger millisecond(final MqlString timezone) { public MqlString asString(final MqlString timezone, final MqlString format) { Assertions.notNull("timezone", timezone); Assertions.notNull("format", format); - return doAsString(timezone, format); + return newMqlExpression((cr) -> astDoc("$dateToString", new BsonDocument() + .append("date", this.toBsonValue(cr)) + .append("format", toBsonValue(cr, format)) + .append("timezone", toBsonValue(cr, timezone)))); } @Override public MqlString asString(final MqlString timezone) { Assertions.notNull("timezone", timezone); - return doAsString(timezone, null); + return newMqlExpression((cr) -> astDoc("$dateToString", new BsonDocument() + .append("date", this.toBsonValue(cr)) + .append("timezone", toBsonValue(cr, timezone)))); } - private MqlString doAsString(final MqlString timezone, @Nullable final MqlString format) { - return newMqlExpression((cr) -> { - BsonDocument document = new BsonDocument("date", this.toBsonValue(cr)); - if (format != null) { - document.append("format", toBsonValue(cr, format)); - } - document.append("timezone", toBsonValue(cr, timezone)); - return astDoc("$dateToString", document); - }); - } @Override public MqlDate parseDate(final MqlString timezone, final MqlString format) { From 0954ac5dde3d243a4854d377b44bee2b0ff8445a Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Fri, 18 Jul 2025 13:58:26 -0400 Subject: [PATCH 4/5] Update driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java Co-authored-by: Maxim Katcharov --- .../main/com/mongodb/client/model/mql/MqlExpression.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java index 765926b0a08..c07c386eeb7 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java @@ -953,14 +953,13 @@ public MqlString asString(final MqlString timezone, final MqlString format) { .append("timezone", toBsonValue(cr, timezone)))); } - @Override public MqlString asString(final MqlString timezone) { - Assertions.notNull("timezone", timezone); - return newMqlExpression((cr) -> astDoc("$dateToString", new BsonDocument() - .append("date", this.toBsonValue(cr)) - .append("timezone", toBsonValue(cr, timezone)))); + fail(); // intentionally not implemented, see DRIVERS-2620 } + public MqlString asString() { + fail(); // intentionally not implemented, see DRIVERS-2620 + } @Override public MqlDate parseDate(final MqlString timezone, final MqlString format) { From 3a4b76b0b98bf93481c442660a1f58b0fe52ffc5 Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Fri, 18 Jul 2025 14:11:35 -0400 Subject: [PATCH 5/5] follow up of the last suggestion commit --- .../client/model/mql/MqlExpression.java | 7 ++--- .../mql/TypeMqlValuesFunctionalTest.java | 30 ------------------- 2 files changed, 2 insertions(+), 35 deletions(-) diff --git a/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java index c07c386eeb7..56108fb1436 100644 --- a/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java @@ -28,6 +28,7 @@ import java.util.function.BinaryOperator; import java.util.function.Function; +import static com.mongodb.assertions.Assertions.fail; import static com.mongodb.client.model.mql.MqlValues.of; import static com.mongodb.client.model.mql.MqlValues.ofNull; import static com.mongodb.client.model.mql.MqlValues.ofStringArray; @@ -954,11 +955,7 @@ public MqlString asString(final MqlString timezone, final MqlString format) { } public MqlString asString(final MqlString timezone) { - fail(); // intentionally not implemented, see DRIVERS-2620 - } - - public MqlString asString() { - fail(); // intentionally not implemented, see DRIVERS-2620 + throw fail(); // intentionally not implemented, see DRIVERS-2620 } @Override diff --git a/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java index 575fd0145dd..9083001cf99 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java @@ -33,7 +33,6 @@ import static com.mongodb.client.model.mql.MqlValues.ofIntegerArray; import static com.mongodb.client.model.mql.MqlValues.ofMap; import static com.mongodb.client.model.mql.MqlValues.ofNull; -import static java.time.format.DateTimeFormatter.ISO_INSTANT; import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -226,35 +225,6 @@ public void dateAsStringTest() { of(instant).asString(of("America/New_York"), of("%Y-%m-%dT%H:%M:%S.%L%z"))); } - /** - * Tests that with server default format and non-UTC timezone, {@code $dateToString} won't output invalid ISO 8601 string ending with 'Z'. - * Ticket: JAVA-5044 - */ - @Test - void dateAsStringWithDefaultFormat() { - // the bug was reported on v6.0.3, but was fixed in v7.1 without backporting - // due to the concern that it is a breaking change. - assumeTrue(serverVersionAtLeast(7, 1)); - - final Instant instant = Instant.parse("2025-07-17T18:00:36.052Z"); - MqlDate date = of(instant); - ZonedDateTime utcDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of(ZoneOffset.UTC.getId())); - - // UTC timezone's default format should be "%Y-%m-%dT%H:%M:%S.%LZ" - assertExpression( - utcDateTime.format(ISO_INSTANT), - date.asString(of("UTC")), - "{'$dateToString': {'date': {'$date': '2025-07-17T18:00:36.052Z'}, " - + "'timezone': 'UTC'}}"); - - // non-UTC timezone's default format should be "%Y-%m-%dT%H:%M:%S.%L" - assertExpression( - utcDateTime.withZoneSameInstant(ZoneId.of("America/New_York")).format(ISO_LOCAL_DATE_TIME), - date.asString(of("America/New_York")), - "{'$dateToString': {'date': {'$date': '2025-07-17T18:00:36.052Z'}, " - + "'timezone': 'America/New_York'}}"); - } - // parse string @Test