Skip to content

Commit d451079

Browse files
Fixed week of week based year handling (#3299) (#3303)
Signed-off-by: Norman Jordan <norman.jordan@improving.com> (cherry picked from commit 7d9f6bd)
1 parent 6fd731b commit d451079

2 files changed

Lines changed: 61 additions & 14 deletions

File tree

core/src/main/java/org/opensearch/sql/expression/datetime/DateTimeFunction.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import java.time.format.DateTimeParseException;
6666
import java.time.format.TextStyle;
6767
import java.time.temporal.ChronoUnit;
68+
import java.time.temporal.IsoFields;
6869
import java.time.temporal.TemporalAmount;
6970
import java.util.Locale;
7071
import java.util.Map;
@@ -125,7 +126,8 @@ public class DateTimeFunction {
125126
.put("MINUTE", "mm")
126127
.put("HOUR", "HH")
127128
.put("DAY", "dd")
128-
.put("WEEK", "w")
129+
// removing "WEEK" to standardize the extract
130+
// .put("WEEK", "w")
129131
.put("MONTH", "MM")
130132
.put("YEAR", "yyyy")
131133
.put("SECOND_MICROSECOND", "ssSSSSSS")
@@ -1585,7 +1587,13 @@ private ExprValue exprDayOfYear(ExprValue date) {
15851587
*/
15861588
public ExprLongValue formatExtractFunction(ExprValue part, ExprValue datetime) {
15871589
String partName = part.stringValue().toUpperCase();
1588-
LocalDateTime arg = datetime.datetimeValue();
1590+
LocalDateTime arg = datetime.datetimeValue().atZone(ZoneOffset.UTC).toLocalDateTime();
1591+
1592+
// Override "Week" to use the IsoFields week-of-week-based-year format
1593+
if (partName.equals("WEEK")) {
1594+
return new ExprLongValue(arg.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR));
1595+
}
1596+
15891597
String text =
15901598
arg.format(DateTimeFormatter.ofPattern(extract_formats.get(partName), Locale.ENGLISH));
15911599

core/src/test/java/org/opensearch/sql/expression/datetime/ExtractTest.java

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@
55

66
package org.opensearch.sql.expression.datetime;
77

8-
import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR;
98
import static org.junit.jupiter.api.Assertions.assertEquals;
109
import static org.opensearch.sql.data.type.ExprCoreType.LONG;
1110

11+
import java.time.Instant;
1212
import java.time.LocalDate;
13+
import java.time.LocalDateTime;
14+
import java.time.LocalTime;
15+
import java.time.ZoneId;
16+
import java.time.temporal.IsoFields;
1317
import java.util.stream.Stream;
1418
import org.junit.jupiter.api.Test;
1519
import org.junit.jupiter.params.ParameterizedTest;
1620
import org.junit.jupiter.params.provider.Arguments;
1721
import org.junit.jupiter.params.provider.MethodSource;
22+
import org.junit.jupiter.params.provider.ValueSource;
1823
import org.opensearch.sql.data.model.ExprDateValue;
1924
import org.opensearch.sql.data.model.ExprDatetimeValue;
2025
import org.opensearch.sql.data.model.ExprTimeValue;
@@ -23,6 +28,7 @@
2328
import org.opensearch.sql.expression.Expression;
2429
import org.opensearch.sql.expression.ExpressionTestBase;
2530
import org.opensearch.sql.expression.FunctionExpression;
31+
import org.opensearch.sql.expression.function.FunctionProperties;
2632

2733
class ExtractTest extends ExpressionTestBase {
2834

@@ -82,9 +88,14 @@ public void testExtractWithDatetime(String part, long expected) {
8288
}
8389

8490
private void datePartWithTimeArgQuery(String part, String time, long expected) {
91+
datePartWithTimeArgQuery(functionProperties, part, time, expected);
92+
}
93+
94+
private void datePartWithTimeArgQuery(
95+
FunctionProperties properties, String part, String time, long expected) {
8596
ExprTimeValue timeValue = new ExprTimeValue(time);
8697
FunctionExpression datetimeExpression =
87-
DSL.extract(functionProperties, DSL.literal(part), DSL.literal(timeValue));
98+
DSL.extract(properties, DSL.literal(part), DSL.literal(timeValue));
8899

89100
assertEquals(LONG, datetimeExpression.type());
90101
assertEquals(expected, eval(datetimeExpression).longValue());
@@ -96,21 +107,49 @@ public void testExtractDatePartWithTimeType() {
96107

97108
datePartWithTimeArgQuery("DAY", timeInput, now.getDayOfMonth());
98109

99-
// To avoid flaky test, skip the testing in December and January because the WEEK is ISO 8601
100-
// week-of-week-based-year which is considered to start on a Monday and week 1 is the first week
101-
// with >3 days. it is possible for early-January dates to be part of the 52nd or 53rd week of
102-
// the previous year, and for late-December dates to be part of the first week of the next year.
103-
// For example, 2005-01-02 is part of the 53rd week of year 2004, while 2012-12-31 is part of
104-
// the first week of 2013
105-
if (now.getMonthValue() != 1 && now.getMonthValue() != 12) {
106-
datePartWithTimeArgQuery("WEEK", datetimeInput, now.get(ALIGNED_WEEK_OF_YEAR));
107-
}
108-
109110
datePartWithTimeArgQuery("MONTH", timeInput, now.getMonthValue());
110111

111112
datePartWithTimeArgQuery("YEAR", timeInput, now.getYear());
112113
}
113114

115+
@ParameterizedTest(name = "{0}")
116+
@ValueSource(
117+
strings = {
118+
"2009-12-26",
119+
"2009-12-27",
120+
"2008-12-28", // Week 52 of week-based-year 2008
121+
"2009-12-29",
122+
"2008-12-29", // Week 1 of week-based-year 2009
123+
"2008-12-31", // Week 1 of week-based-year 2009
124+
"2009-01-01", // Week 1 of week-based-year 2009
125+
"2009-01-04", // Week 1 of week-based-year 2009
126+
"2009-01-05", // Week 2 of week-based-year 2009
127+
"2025-12-27", // year with 52 weeks
128+
"2026-01-01", // year starts on a THURSDAY
129+
"2028-12-30", // year with 53 weeks
130+
"2028-12-31", // year starts in December
131+
"2029-01-01",
132+
"2033-12-31", // year with 53 weeks
133+
"2034-01-01", // January 1st on a SUNDAY
134+
"2034-12-30", // year with 52 weeks
135+
"2034-12-31"
136+
})
137+
public void testExtractWeekPartWithTimeType(String arg) {
138+
139+
// setup default date/time properties for the extract function
140+
ZoneId currentZoneId = ZoneId.systemDefault();
141+
Instant nowInstant =
142+
LocalDate.parse(arg).atTime(LocalTime.parse(timeInput)).atZone(currentZoneId).toInstant();
143+
FunctionProperties properties = new FunctionProperties(nowInstant, currentZoneId);
144+
145+
// Expected WEEK value should be formated from week-of-week-based-year
146+
LocalDateTime localDateTime = LocalDateTime.ofInstant(nowInstant, currentZoneId);
147+
int expected = localDateTime.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
148+
149+
// verify
150+
datePartWithTimeArgQuery(properties, "WEEK", timeInput, expected);
151+
}
152+
114153
@ParameterizedTest(name = "{0}")
115154
@MethodSource("getDateResultsForExtractFunction")
116155
public void testExtractWithDate(String part, long expected) {

0 commit comments

Comments
 (0)