Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding convert_tz and datetime functions to SQL and PPL #109

Merged

Conversation

MitchellGale
Copy link

Description

PR adds support for convert_tz and Datetime for PPL and SQL languages.

convert_tz

Syntax for convert_tz:
convert_tz(datetime string, from timezone, to timezone)
It converts the datetime string from the from timezone to the to timezone. Converts from negative to positive timezone, from positive to negative, from positive to positive or negative to negative time zones. Works across year roll-over and leap years.
Example:

opensearchsql> SELECT convert_tz('2021-05-12 00:00:00','-12:00','+13:00'); 
fetched rows / total rows = 1/1
+-------------------------------------------------------+
| convert_tz('2021-05-12 00:00:00','-12:00','+13:00')   |
|-------------------------------------------------------|
| 2021-05-13 01:00:00                                   |
+-------------------------------------------------------+
opensearchsql> SELECT convert_tz('2021-05-12 12:34:56','-00:00','+01:00');
fetched rows / total rows = 1/1
+-------------------------------------------------------+
| convert_tz('2021-05-12 12:34:56','-00:00','+01:00')   |
|-------------------------------------------------------|
| 2021-05-12 13:34:56                                   |
+-------------------------------------------------------+

nulls

It returns null for time zones outside of the allowed range (allow range is -12:00 -> +14:00)

opensearchsql> SELECT convert_tz('2021-05-12 12:34:56','-00:00','+15:00');
fetched rows / total rows = 1/1
+-------------------------------------------------------+
| convert_tz('2021-05-12 12:34:56','-00:00','+15:00')   |
|-------------------------------------------------------|
| null                                                  |
+-------------------------------------------------------+
opensearchsql> SELECT convert_tz('2021-05-12 12:34:56','-13:00','+10:00');
fetched rows / total rows = 1/1
+-------------------------------------------------------+
| convert_tz('2021-05-12 12:34:56','-13:00','+10:00')   |
|-------------------------------------------------------|
| null                                                  |
+-------------------------------------------------------+

Leap year

opensearchsql> SELECT convert_tz('2005-02-28 20:00:00','-10:00','+10:00');
fetched rows / total rows = 1/1
+-------------------------------------------------------+
| convert_tz('2005-02-28 20:00:00','-10:00','+10:00')   |
|-------------------------------------------------------|
| 2005-03-01 16:00:00                                   |
+-------------------------------------------------------+
opensearchsql> SELECT convert_tz('2004-02-28 20:00:00','-10:00','+10:00');
fetched rows / total rows = 1/1
+-------------------------------------------------------+
| convert_tz('2004-02-28 20:00:00','-10:00','+10:00')   |
|-------------------------------------------------------|
| 2004-02-29 16:00:00                                   |
+-------------------------------------------------------+

DateTime

DATETIME(timestamp_expression [, time_zone])

fetched rows / total rows = 1/1
+---------------------------------------------------+
| DATETIME('2008-12-25 05:30:00-05:00', '+05:00')   |
|---------------------------------------------------|
| 2008-12-25 15:30:00                               |
+---------------------------------------------------+
opensearchsql> SELECT DATETIME('2008-12-25 05:30:00', '+05:00');
fetched rows / total rows = 1/1
+---------------------------------------------+
| DATETIME('2008-12-25 05:30:00', '+05:00')   |
|---------------------------------------------|
| 2008-12-25 10:30:00                         |
+---------------------------------------------+
opensearchsql> SELECT DATETIME('2008-12-25 05:30:00+10:00'); 
fetched rows / total rows = 1/1
+-----------------------------------------+
| DATETIME('2008-12-25 05:30:00+10:00')   |
|-----------------------------------------|
| 2008-12-25 05:30:00                     |
+-----------------------------------------+
opensearchsql> SELECT DATETIME('2008-12-25 05:30:00');
fetched rows / total rows = 1/1
+-----------------------------------+
| DATETIME('2008-12-25 05:30:00')   |
|-----------------------------------|
| 2008-12-25 05:30:00               |
+-----------------------------------+

Issues Resolved

#703
#46

Check List

  • New functionality includes testing.
    • All tests pass, including unit test, integration test and doctest
  • New functionality has been documented.
    • New functionality has javadoc added
    • New functionality has user manual doc added
  • Commits are signed per the DCO using --signoff

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

@codecov
Copy link

codecov bot commented Aug 19, 2022

Codecov Report

Merging #109 (45a6d2e) into integ-Add-convert_tz-function (929ebfe) will increase coverage by 0.04%.
The diff coverage is 100.00%.

@@                         Coverage Diff                         @@
##             integ-Add-convert_tz-function     #109      +/-   ##
===================================================================
+ Coverage                            94.83%   94.87%   +0.04%     
- Complexity                            2916     2932      +16     
===================================================================
  Files                                  291      291              
  Lines                                 7795     7857      +62     
  Branches                               567      574       +7     
===================================================================
+ Hits                                  7392     7454      +62     
  Misses                                 349      349              
  Partials                                54       54              
Flag Coverage Δ
query-workbench 62.76% <ø> (ø)
sql-engine 97.80% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
...g/opensearch/sql/data/model/ExprDatetimeValue.java 100.00% <100.00%> (ø)
...c/main/java/org/opensearch/sql/expression/DSL.java 100.00% <100.00%> (ø)
...arch/sql/expression/datetime/DateTimeFunction.java 100.00% <100.00%> (ø)
...h/sql/expression/function/BuiltinFunctionName.java 100.00% <100.00%> (ø)

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@Yury-Fridlyand
Copy link

  • Add tests with non-round-hour-offset timzones, like Asia/Katmandu +05:45, Asia/Kabul +04:30, Australia/Broken_Hill +09:30.
  • And a test where TZ shift is exactly 24 hours - you should get the same time, but on another date.
  • Non existing TZ

Other optional tests:

  • Switch between winter/summer timezones when they have shift
  • Switch from a TZ to the same TZ

Please, keep in mind, we don't test java, but we should ensure that user will get a valid result or a clear error message of incorrect input/limitation.

@@ -679,4 +780,29 @@ private ExprValue exprYear(ExprValue date) {
return new ExprIntegerValue(date.dateValue().getYear());
}

private Boolean isTimeZoneValid(ZoneId zone) {

Choose a reason for hiding this comment

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

Isn't every ZoneId a valid time zone identifier? Why do we need this validation?

If we do, needs unit tests to show which subset of ZoneIds is valid and which are invalid.

Copy link
Author

Choose a reason for hiding this comment

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

The valid/invalid test is just to check if the timezone is outside of the range
+14 and - 13:59. Also needs validation if the input was actually a valid timezone, if not it should return null too. Current commit doesn't have it, being pushed soon.

Choose a reason for hiding this comment

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

I'd like to follow-up on this. As far as I can see, in Java, every ZoneId instance is a valid time zone.

Copy link
Author

@MitchellGale MitchellGale Sep 2, 2022

Choose a reason for hiding this comment

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

mysql> select convert_tz("2000-01-01 10:00:00", "+14:00", "+00:00");
+-------------------------------------------------------+
| convert_tz("2000-01-01 10:00:00", "+14:00", "+00:00") |
+-------------------------------------------------------+
| 1999-12-31 20:00:00                                   |
+-------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select convert_tz("2000-01-01 10:00:00", "+14:01", "+00:00");
+-------------------------------------------------------+
| convert_tz("2000-01-01 10:00:00", "+14:01", "+00:00") |
+-------------------------------------------------------+
| NULL                                                  |
+-------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select convert_tz("2000-01-01 10:00:00", "-13:59", "+00:00");
+-------------------------------------------------------+
| convert_tz("2000-01-01 10:00:00", "-13:59", "+00:00") |
+-------------------------------------------------------+
| 2000-01-01 23:59:00                                   |
+-------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select convert_tz("2000-01-01 10:00:00", "-14:00", "+00:00");
+-------------------------------------------------------+
| convert_tz("2000-01-01 10:00:00", "-14:00", "+00:00") |
+-------------------------------------------------------+
| NULL                                                  |
+-------------------------------------------------------+
1 row in set (0.00 sec)

The validator is to validate the timezones that match with MySQL standard. Java can handle all timezones being accepted and converted, but it won't match MySQL standard.

Choose a reason for hiding this comment

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

If that's the case, we need to rename this as isValidMySqlTimeZoneId is similar

Choose a reason for hiding this comment

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

See my comment above where the function is being used.

Copy link
Author

Choose a reason for hiding this comment

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

Function name updated to isValidMySqlTimeZoneId.

@Yury-Fridlyand
Copy link

opensearchsql> SELECT convert_tz('2021-02-30 12:34:56','UTC','+03:00');
fetched rows / total rows = 1/1
+----------------------------------------------------+
| convert_tz('2021-02-30 12:34:56','UTC','+03:00')   |
|----------------------------------------------------|
| 2021-02-28 15:34:56                                |
+----------------------------------------------------+
opensearchsql> SELECT DATETIME('2021-02-30 12:34:56');
fetched rows / total rows = 1/1
+-----------------------------------+
| DATETIME('2021-02-30 12:34:56')   |
|-----------------------------------|
| 2021-02-28 12:34:56               |
+-----------------------------------+

Queries on Feb 30 don't fail. A bug?

Comment on lines 532 to 557
/**
* DateTime implementation for ExprValue.
*
* @param dateTime ExprValue of String type.
* @return ExprValue of date type.
*/

Choose a reason for hiding this comment

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

Please, describe both params in javadoc

Copy link
Author

Choose a reason for hiding this comment

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

Second parameter added to java doc. Java doc also added to exprDateTimeNoTimezone function.

@@ -463,4 +462,4 @@ public void testDateFormatISO8601() throws IOException {
String dateFormatted = "1998-01-31T00:00:00Z";
verifyDateFormat(date, "date", dateFormat, dateFormatted);
}
}
}

Choose a reason for hiding this comment

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

newline would be great here.

* @param dateTime ExprValue of String type.
* @return ExprValue of date type.
*/
private ExprValue exprDateTime(ExprValue dateTime, ExprValue timeZone) {

Choose a reason for hiding this comment

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

Let's discuss this method. I'd like to understand a few things:

  • How do we know dateTime can be successfully parsed? This is done in two places without try-catch, yet it was parsing was wrapped in try-catch elsewhere.
  • We are checking if timeZone is null but not dateTime -- how come?
  • If timeZone.isNull block is special case only for exprDateTimeNoTZ then it needs to move to that method.
  • try block on line 549 guards against parse on line 550 failing? Then why is it safe to assume parse call on line 558 will succeed?
  • What is the difference between LocalDateTime and default time zone?

Copy link
Author

Choose a reason for hiding this comment

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

We are checking if timeZone is null but not dateTime -- how come?

The null field will come from exprDateTimeNoTimezone which is used when only one parameter to datetime is called.

If I call SELECT DATETIME('2021-02-31 12:34:56) then exprDateTimeNoTimezone calls exprDateTime with null in the timezone field.

Choose a reason for hiding this comment

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

A better pattern would be to have function overloads: one with date and timezone, and one without timezone.
As it stands, you should include the null as an expected/handled expected @param value for timeZone.

@@ -679,4 +780,29 @@ private ExprValue exprYear(ExprValue date) {
return new ExprIntegerValue(date.dateValue().getYear());
}

private Boolean isTimeZoneValid(ZoneId zone) {

Choose a reason for hiding this comment

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

I'd like to follow-up on this. As far as I can see, in Java, every ZoneId instance is a valid time zone.

@MaxKsyunz MaxKsyunz self-requested a review September 8, 2022 00:21
Comment on lines +554 to +564
DateTimeFormatter formatDT = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss[xxx]")
.withResolverStyle(ResolverStyle.STRICT);

try {
LocalDateTime ldtFormatted = LocalDateTime.parse(dateTime.stringValue(), formatDT);

Choose a reason for hiding this comment

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

You can use new ExprDatetimeValue(dateTime.stringValue()) and don't worry about parser and format.
In that case you just need to update ExprDatetimeValue ctor to use ResolverStyle.STRICT - it will be good in any case.

Copy link
Author

Choose a reason for hiding this comment

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

I don't understand what the benefit of this change would be.

Choose a reason for hiding this comment

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

You can reduce a bit your code an delegate parsing to code that already exists and covered by tests.

Copy link
Author

Choose a reason for hiding this comment

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

Resolved.

Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
…field number.

Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
…est in ConvertTZTest.java.

Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
Signed-off-by: MitchellGale-BitQuill <mitchellg@bitquilltech.com>
@MitchellGale MitchellGale force-pushed the dev-Add-convert_tz-function branch from bb409a4 to 45a6d2e Compare September 19, 2022 22:51
@MitchellGale MitchellGale merged commit f19780b into integ-Add-convert_tz-function Sep 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants