Skip to content

Commit 281c7a3

Browse files
make format optional and create testing case about $dateToString aggregation with timezone outputs invalid ISO8601 string
1 parent 5f2c539 commit 281c7a3

File tree

3 files changed

+63
-4
lines changed

3 files changed

+63
-4
lines changed

driver-core/src/main/com/mongodb/client/model/mql/MqlDate.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,20 @@ public interface MqlDate extends MqlValue {
138138
*/
139139
MqlString asString(MqlString timezone, MqlString format);
140140

141+
/**
142+
* The string representation of {@code this} date as determined by the
143+
* provided {@code timezone} with default format as below:
144+
*
145+
* <ul>
146+
* <li>If {@code timezone} is UTC timezone, {@code "%Y-%m-%dT%H:%M:%S.%LZ"} will be the default
147+
* <li>Otherwise the default format will be {@code "%Y-%m-%dT%H:%M:%S.%L"}
148+
* </ul>
149+
*
150+
* @param timezone the UTC Offset or Olson Timezone Identifier.
151+
* @return the resulting value.
152+
*/
153+
MqlString asString(MqlString timezone);
154+
141155
/**
142156
* The result of passing {@code this} value to the provided function.
143157
* Equivalent to {@code f.apply(this)}, and allows lambdas and static,

driver-core/src/main/com/mongodb/client/model/mql/MqlExpression.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.mongodb.client.model.mql;
1818

1919
import com.mongodb.assertions.Assertions;
20+
import com.mongodb.lang.Nullable;
2021
import org.bson.BsonArray;
2122
import org.bson.BsonDocument;
2223
import org.bson.BsonInt32;
@@ -947,10 +948,24 @@ public MqlInteger millisecond(final MqlString timezone) {
947948
public MqlString asString(final MqlString timezone, final MqlString format) {
948949
Assertions.notNull("timezone", timezone);
949950
Assertions.notNull("format", format);
950-
return newMqlExpression((cr) -> astDoc("$dateToString", new BsonDocument()
951-
.append("date", this.toBsonValue(cr))
952-
.append("format", toBsonValue(cr, format))
953-
.append("timezone", toBsonValue(cr, timezone))));
951+
return doAsString(timezone, format);
952+
}
953+
954+
@Override
955+
public MqlString asString(final MqlString timezone) {
956+
Assertions.notNull("timezone", timezone);
957+
return doAsString(timezone, null);
958+
}
959+
960+
private MqlString doAsString(final MqlString timezone, @Nullable final MqlString format) {
961+
return newMqlExpression((cr) -> {
962+
BsonDocument document = new BsonDocument("date", this.toBsonValue(cr));
963+
if (format != null) {
964+
document.append("format", toBsonValue(cr, format));
965+
}
966+
document.append("timezone", toBsonValue(cr, timezone));
967+
return astDoc("$dateToString", document);
968+
});
954969
}
955970

956971
@Override

driver-core/src/test/functional/com/mongodb/client/model/mql/TypeMqlValuesFunctionalTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import static com.mongodb.client.model.mql.MqlValues.ofIntegerArray;
3434
import static com.mongodb.client.model.mql.MqlValues.ofMap;
3535
import static com.mongodb.client.model.mql.MqlValues.ofNull;
36+
import static java.time.format.DateTimeFormatter.ISO_INSTANT;
3637
import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME;
3738
import static org.junit.jupiter.api.Assertions.assertThrows;
3839
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@@ -225,6 +226,35 @@ public void dateAsStringTest() {
225226
of(instant).asString(of("America/New_York"), of("%Y-%m-%dT%H:%M:%S.%L%z")));
226227
}
227228

229+
/**
230+
* Tests that with default format and non-UTC timezone, {@code $dateToString} won't output invalid ISO 8601 string ending with 'Z'.
231+
* Ticket: JAVA-5044
232+
*/
233+
@Test
234+
void dateAsStringWithDefaultFormat() {
235+
// the bug was reported on v6.0.3, but was fixed in v7.1 without backporting
236+
// due to the concern that it is a breaking change.
237+
assumeTrue(serverVersionAtLeast(7, 1));
238+
239+
final Instant instant = Instant.parse("2025-07-17T18:00:36.052Z");
240+
MqlDate date = of(instant);
241+
ZonedDateTime utcDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of(ZoneOffset.UTC.getId()));
242+
243+
// UTC timezone's default format should be "%Y-%m-%dT%H:%M:%S.%LZ"
244+
assertExpression(
245+
utcDateTime.format(ISO_INSTANT),
246+
date.asString(of("UTC")),
247+
"{'$dateToString': {'date': {'$date': '2025-07-17T18:00:36.052Z'}, "
248+
+ "'timezone': 'UTC'}}");
249+
250+
// non-UTC timezone's default format should be "%Y-%m-%dT%H:%M:%S.%L"
251+
assertExpression(
252+
utcDateTime.withZoneSameInstant(ZoneId.of("America/New_York")).format(ISO_LOCAL_DATE_TIME),
253+
date.asString(of("America/New_York")),
254+
"{'$dateToString': {'date': {'$date': '2025-07-17T18:00:36.052Z'}, "
255+
+ "'timezone': 'America/New_York'}}");
256+
}
257+
228258
// parse string
229259

230260
@Test

0 commit comments

Comments
 (0)