Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Support date and time function: week #757

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,10 @@ public FunctionExpression to_days(Expression... expressions) {
return function(BuiltinFunctionName.TO_DAYS, expressions);
}

public FunctionExpression week(Expression... expressions) {
return function(BuiltinFunctionName.WEEK, expressions);
}

public FunctionExpression year(Expression... expressions) {
return function(BuiltinFunctionName.YEAR, expressions);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file 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 com.amazon.opendistroforelasticsearch.sql.expression.datetime;

import com.amazon.opendistroforelasticsearch.sql.data.model.ExprValue;
import com.amazon.opendistroforelasticsearch.sql.exception.SemanticCheckException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

public class CalendarLookup {

private Map<Integer, Calendar> map = new HashMap<>();

/**
* Set Calendar in map for all modes.
* @param date ExprValue of Date/Datetime/Timestamp/String type.
*/
public CalendarLookup(ExprValue date) {
map.put(0, getCalendar(Calendar.SUNDAY, 7, date));
map.put(1, getCalendar(Calendar.MONDAY, 5, date));
map.put(2, getCalendar(Calendar.SUNDAY, 7, date));
map.put(3, getCalendar(Calendar.MONDAY, 5, date));
map.put(4, getCalendar(Calendar.SUNDAY, 4, date));
map.put(5, getCalendar(Calendar.MONDAY, 7, date));
map.put(6, getCalendar(Calendar.SUNDAY, 4, date));
map.put(7, getCalendar(Calendar.MONDAY, 7, date));
}

/**
* Set first day of week, minimal days in first week and date in calendar.
* @param firstDayOfWeek the given first day of the week.
* @param minimalDaysInWeek the given minimal days required in the first week of the year.
* @param date the ExprValue of Date/Datetime/Timestamp/String type.
*/
private Calendar getCalendar(int firstDayOfWeek, int minimalDaysInWeek, ExprValue date) {
Calendar calendar = Calendar.getInstance();
calendar.setFirstDayOfWeek(firstDayOfWeek);
calendar.setMinimalDaysInFirstWeek(minimalDaysInWeek);
calendar.set(date.dateValue().getYear(), date.dateValue().getMonthValue() - 1,
date.dateValue().getDayOfMonth());
return calendar;
}

/**
* Returns week number for date according to mode.
* @param mode Integer for mode. Valid mode values are 0 to 7.
*/
public int getWeekNumber(int mode) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this function be static to avoid consturct CalendarLookup every time?
e.g. getWeekNumber(ExprValue date, ExprValue mode)
then define getCalendar(int firstDayOfWeek, int minimalDaysInWeek, Supplier dateProvider)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. added this change.

if (map.containsKey(mode)) {
int weekNumber = map.get(mode).get(Calendar.WEEK_OF_YEAR);
if ((weekNumber > 51)
&& (map.get(mode).get(Calendar.DAY_OF_MONTH) < 7)
&& Arrays.asList(0, 1, 4, 5).contains(mode)) {
weekNumber = 0;
}
return weekNumber;
}
throw new SemanticCheckException(
String.format("mode:%s is invalid, please use mode value between 0-7", mode));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public void register(BuiltinFunctionRepository repository) {
repository.register(time_to_sec());
repository.register(timestamp());
repository.register(to_days());
repository.register(week());
repository.register(year());
}

Expand Down Expand Up @@ -371,6 +372,22 @@ private FunctionResolver to_days() {
impl(nullMissingHandling(DateTimeFunction::exprToDays), LONG, DATETIME));
}

/**
* WEEK(DATE[,mode]). return the week number for date.
*/
private FunctionResolver week() {
return define(BuiltinFunctionName.WEEK.getName(),
impl(nullMissingHandling(DateTimeFunction::exprWeekWithoutMode), INTEGER, DATE),
impl(nullMissingHandling(DateTimeFunction::exprWeekWithoutMode), INTEGER, DATETIME),
impl(nullMissingHandling(DateTimeFunction::exprWeekWithoutMode), INTEGER, TIMESTAMP),
impl(nullMissingHandling(DateTimeFunction::exprWeekWithoutMode), INTEGER, STRING),
impl(nullMissingHandling(DateTimeFunction::exprWeek), INTEGER, DATE, INTEGER),
impl(nullMissingHandling(DateTimeFunction::exprWeek), INTEGER, DATETIME, INTEGER),
impl(nullMissingHandling(DateTimeFunction::exprWeek), INTEGER, TIMESTAMP, INTEGER),
impl(nullMissingHandling(DateTimeFunction::exprWeek), INTEGER, STRING, INTEGER)
);
}

/**
* YEAR(STRING/DATE/DATETIME/TIMESTAMP). return the year for date (1000-9999).
*/
Expand Down Expand Up @@ -621,6 +638,26 @@ private ExprValue exprToDays(ExprValue date) {
return new ExprLongValue(date.dateValue().toEpochDay() + DAYS_0000_TO_1970);
}

/**
* Week for date implementation for ExprValue.
* @param date ExprValue of Date/Datetime/Timestamp/String type.
* @param mode ExprValue of Integer type.
*/
private ExprValue exprWeek(ExprValue date, ExprValue mode) {
CalendarLookup calendarLookup = new CalendarLookup(date);
return new ExprIntegerValue(calendarLookup.getWeekNumber(mode.integerValue()));
}

/**
* Week for date implementation for ExprValue.
* When mode is not specified default value mode 0 is used for default_week_format.
* @param date ExprValue of Date/Datetime/Timestamp/String type.
* @return ExprValue.
*/
private ExprValue exprWeekWithoutMode(ExprValue date) {
return exprWeek(date, new ExprIntegerValue(0));
}

/**
* Year for date implementation for ExprValue.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public enum BuiltinFunctionName {
TIME_TO_SEC(FunctionName.of("time_to_sec")),
TIMESTAMP(FunctionName.of("timestamp")),
TO_DAYS(FunctionName.of("to_days")),
WEEK(FunctionName.of("week")),
YEAR(FunctionName.of("year")),

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.TIME;
import static com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType.TIMESTAMP;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;

import com.amazon.opendistroforelasticsearch.sql.data.model.ExprDateValue;
Expand All @@ -39,6 +40,8 @@
import com.amazon.opendistroforelasticsearch.sql.data.model.ExprTimeValue;
import com.amazon.opendistroforelasticsearch.sql.data.model.ExprTimestampValue;
import com.amazon.opendistroforelasticsearch.sql.data.model.ExprValue;
import com.amazon.opendistroforelasticsearch.sql.data.type.ExprCoreType;
import com.amazon.opendistroforelasticsearch.sql.exception.SemanticCheckException;
import com.amazon.opendistroforelasticsearch.sql.expression.DSL;
import com.amazon.opendistroforelasticsearch.sql.expression.Expression;
import com.amazon.opendistroforelasticsearch.sql.expression.ExpressionTestBase;
Expand Down Expand Up @@ -726,6 +729,100 @@ public void timestamp() {
assertEquals("timestamp(TIMESTAMP '2020-08-17 01:01:01')", expr.toString());
}

private void testWeek(String date, int mode, int expectedResult) {
FunctionExpression expression = dsl
.week(DSL.literal(new ExprDateValue(date)), DSL.literal(mode));
assertEquals(INTEGER, expression.type());
assertEquals(String.format("week(DATE '%s', %d)", date, mode), expression.toString());
assertEquals(integerValue(expectedResult), eval(expression));
}

private void testNullMissingWeek(ExprCoreType date) {
when(nullRef.type()).thenReturn(date);
when(missingRef.type()).thenReturn(date);
assertEquals(nullValue(), eval(dsl.week(nullRef)));
assertEquals(missingValue(), eval(dsl.week(missingRef)));
}

@Test
public void week() {
testNullMissingWeek(DATE);
testNullMissingWeek(DATETIME);
testNullMissingWeek(TIMESTAMP);
testNullMissingWeek(STRING);

when(nullRef.type()).thenReturn(INTEGER);
when(missingRef.type()).thenReturn(INTEGER);
assertEquals(nullValue(), eval(dsl.week(DSL.literal("2019-01-05"), nullRef)));
assertEquals(missingValue(), eval(dsl.week(DSL.literal("2019-01-05"), missingRef)));

when(nullRef.type()).thenReturn(DATE);
when(missingRef.type()).thenReturn(INTEGER);
assertEquals(missingValue(), eval(dsl.week(nullRef, missingRef)));

chloe-zh marked this conversation as resolved.
Show resolved Hide resolved
FunctionExpression expression = dsl
.week(DSL.literal(new ExprTimestampValue("2019-01-05 01:02:03")));
assertEquals(INTEGER, expression.type());
assertEquals("week(TIMESTAMP '2019-01-05 01:02:03')", expression.toString());
assertEquals(integerValue(0), eval(expression));

expression = dsl.week(DSL.literal("2019-01-05"));
assertEquals(INTEGER, expression.type());
assertEquals("week(\"2019-01-05\")", expression.toString());
assertEquals(integerValue(0), eval(expression));

expression = dsl.week(DSL.literal("2019-01-05 00:01:00"));
assertEquals(INTEGER, expression.type());
assertEquals("week(\"2019-01-05 00:01:00\")", expression.toString());
assertEquals(integerValue(0), eval(expression));

testWeek("2019-01-05", 0, 0);
testWeek("2019-01-05", 1, 1);
testWeek("2019-01-05", 2, 52);
testWeek("2019-01-05", 3, 1);
testWeek("2019-01-05", 4, 1);
testWeek("2019-01-05", 5, 0);
testWeek("2019-01-05", 6, 1);
testWeek("2019-01-05", 7, 53);

testWeek("2019-01-06", 0, 1);
testWeek("2019-01-06", 1, 1);
testWeek("2019-01-06", 2, 1);
testWeek("2019-01-06", 3, 1);
testWeek("2019-01-06", 4, 2);
testWeek("2019-01-06", 5, 0);
testWeek("2019-01-06", 6, 2);
testWeek("2019-01-06", 7, 53);

testWeek("2019-01-07", 0, 1);
testWeek("2019-01-07", 1, 2);
testWeek("2019-01-07", 2, 1);
testWeek("2019-01-07", 3, 2);
testWeek("2019-01-07", 4, 2);
testWeek("2019-01-07", 5, 1);
testWeek("2019-01-07", 6, 2);
testWeek("2019-01-07", 7, 1);

testWeek("2000-01-01", 0, 0);
testWeek("2000-01-01", 2, 52);
testWeek("1999-12-31", 0, 52);
}

@Test
public void modeInUnsupportedFormat() {
when(nullRef.type()).thenReturn(DATE);
when(missingRef.type()).thenReturn(DATE);
assertEquals(nullValue(), eval(dsl.week(nullRef)));
assertEquals(missingValue(), eval(dsl.week(missingRef)));

FunctionExpression expression = dsl
.week(DSL.literal(new ExprDateValue("2019-01-05")), DSL.literal(8));
SemanticCheckException exception =
assertThrows(SemanticCheckException.class, () -> eval(expression));
assertEquals("mode:8 is invalid, please use mode value between 0-7",
exception.getMessage());
}

@Test
public void to_days() {
when(nullRef.type()).thenReturn(DATE);
Expand Down
63 changes: 63 additions & 0 deletions docs/user/dql/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1357,6 +1357,69 @@ Example::
+------------------------------+


WEEK
----

Description
>>>>>>>>>>>

Usage: week(date[, mode]) returns the week number for date. If the mode argument is omitted, the default mode 0 is used.

.. list-table:: The following table describes how the mode argument works.
:widths: 25 50 25 75
:header-rows: 1

* - Mode
- First day of week
- Range
- Week 1 is the first week …
* - 0
- Sunday
- 0-53
- with a Sunday in this year
* - 1
- Monday
- 0-53
- with 4 or more days this year
* - 2
- Sunday
- 1-53
- with a Sunday in this year
* - 3
- Monday
- 1-53
- with 4 or more days this year
* - 4
- Sunday
- 0-53
- with 4 or more days this year
* - 5
- Monday
- 0-53
- with a Monday in this year
* - 6
- Sunday
- 1-53
- with 4 or more days this year
* - 7
- Monday
- 1-53
- with a Monday in this year

Argument type: DATE/DATETIME/TIMESTAMP/STRING

Return type: INTEGER

Example::

>od SELECT WEEK(DATE('2008-02-20')), WEEK(DATE('2008-02-20'), 1)
fetched rows / total rows = 1/1
+----------------------------+-------------------------------+
| WEEK(DATE('2008-02-20')) | WEEK(DATE('2008-02-20'), 1) |
|----------------------------|-------------------------------|
| 7 | 8 |
+----------------------------+-------------------------------+


YEAR
----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,27 @@ public void testToDays() throws IOException {
verifySome(result.getJSONArray("datarows"), rows(738049));
}

private void week(String date, int mode, int expectedResult) throws IOException {
JSONObject result = executeQuery(String.format(
"source=%s | eval f = week(date('%s'), %d) | fields f", TEST_INDEX_DATE, date, mode));
verifySchema(result, schema("f", null, "integer"));
verifySome(result.getJSONArray("datarows"), rows(expectedResult));
}

@Test
public void testWeek() throws IOException {
JSONObject result = executeQuery(String.format(
"source=%s | eval f = week(date('2008-02-20')) | fields f", TEST_INDEX_DATE));
verifySchema(result, schema("f", null, "integer"));
verifySome(result.getJSONArray("datarows"), rows(7));

week("2008-02-20", 0, 7);
week("2008-02-20", 1, 8);
week("2008-12-31", 1, 53);
week("2000-01-01", 0, 0);
week("2000-01-01", 2, 52);
}

@Test
public void testYear() throws IOException {
JSONObject result = executeQuery(String.format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,26 @@ public void testYear() throws IOException {
verifyDataRows(result, rows(2020));
}

private void week(String date, int mode, int expectedResult) throws IOException {
JSONObject result = executeQuery(String.format("select week(date('%s'), %d)", date, mode));
verifySchema(result,
schema(String.format("week(date('%s'), %d)", date, mode), null, "integer"));
verifyDataRows(result, rows(expectedResult));
}

@Test
public void testWeek() throws IOException {
JSONObject result = executeQuery("select week(date('2008-02-20'))");
verifySchema(result, schema("week(date('2008-02-20'))", null, "integer"));
verifyDataRows(result, rows(7));

week("2008-02-20", 0, 7);
week("2008-02-20", 1, 8);
week("2008-12-31", 1, 53);
week("2000-01-01", 0, 0);
week("2000-01-01", 2, 52);
}

protected JSONObject executeQuery(String query) throws IOException {
Request request = new Request("POST", QUERY_API_ENDPOINT);
request.setJsonEntity(String.format(Locale.ROOT, "{\n" + " \"query\": \"%s\"\n" + "}", query));
Expand Down
Loading