[feature](datetime) Support timezone when insert datetime value (#21898)

This commit is contained in:
zclllyybb
2023-07-31 13:08:28 +08:00
committed by GitHub
parent acc24df10a
commit f2919567df
22 changed files with 624 additions and 100 deletions

View File

@ -25,6 +25,7 @@ import org.apache.doris.catalog.ScalarType;
import org.apache.doris.catalog.Type;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.InvalidFormatException;
import org.apache.doris.nereids.util.DateUtils;
import org.apache.doris.thrift.TDateLiteral;
import org.apache.doris.thrift.TExprNode;
import org.apache.doris.thrift.TExprNodeType;
@ -175,6 +176,7 @@ public class DateLiteral extends LiteralExpr {
//Regex used to determine if the TIME field exists int date_format
private static final Pattern HAS_TIME_PART = Pattern.compile("^.*[HhIiklrSsTp]+.*$");
private static final Pattern HAS_OFFSET_PART = Pattern.compile("[\\+\\-]\\d{2}:\\d{2}");
//Date Literal persist type in meta
private enum DateLiteralType {
@ -358,6 +360,27 @@ public class DateLiteral extends LiteralExpr {
Preconditions.checkArgument(type.isDateType());
TemporalAccessor dateTime = null;
boolean parsed = false;
int offset = 0;
// parse timezone
if (haveTimeZoneOffset(s) || haveTimeZoneName(s)) {
String tzString = new String();
if (haveTimeZoneName(s)) { // GMT, UTC+8, Z[, CN, Asia/Shanghai]
int split = getTimeZoneSplitPos(s);
Preconditions.checkArgument(split > 0);
tzString = s.substring(split);
s = s.substring(0, split);
} else { // +04:30
Preconditions.checkArgument(s.charAt(s.length() - 6) == '-' || s.charAt(s.length() - 6) == '+');
tzString = s.substring(s.length() - 6);
s = s.substring(0, s.length() - 6);
}
ZoneId zone = ZoneId.of(tzString);
ZoneId dorisZone = DateUtils.getTimeZone();
offset = dorisZone.getRules().getOffset(java.time.Instant.now()).getTotalSeconds()
- zone.getRules().getOffset(java.time.Instant.now()).getTotalSeconds();
}
if (!s.contains("-")) {
// handle format like 20210106, but should not handle 2021-1-6
for (DateTimeFormatter formatter : formatterList) {
@ -455,6 +478,17 @@ public class DateLiteral extends LiteralExpr {
type = ScalarType.createDatetimeV2Type(scale);
}
this.type = type;
if (offset != 0) {
DateLiteral result = this.plusSeconds(offset);
this.second = result.second;
this.minute = result.minute;
this.hour = result.hour;
this.day = result.day;
this.month = result.month;
this.year = result.year;
}
if (checkRange() || checkDate()) {
throw new AnalysisException("Datetime value is out of range");
}
@ -1770,4 +1804,27 @@ public class DateLiteral extends LiteralExpr {
return;
}
}
private static boolean haveTimeZoneOffset(String arg) {
Preconditions.checkArgument(arg.length() > 6);
return HAS_OFFSET_PART.matcher(arg.substring(arg.length() - 6)).matches();
}
private static boolean haveTimeZoneName(String arg) {
for (char ch : arg.toCharArray()) {
if (Character.isUpperCase(ch) && ch != 'T') {
return true;
}
}
return false;
}
private static int getTimeZoneSplitPos(String arg) {
int split = arg.length() - 1;
for (; !Character.isAlphabetic(arg.charAt(split)); split--) {
} // skip +8 of UTC+8
for (; split >= 0 && (Character.isUpperCase(arg.charAt(split)) || arg.charAt(split) == '/'); split--) {
}
return split + 1;
}
}

View File

@ -261,7 +261,7 @@ public class StringLiteral extends LiteralExpr {
} else if (targetType.isDateType()) {
// FE only support 'yyyy-MM-dd hh:mm:ss' && 'yyyy-MM-dd' format
// so if FE unchecked cast fail, we also build CastExpr for BE
// BE support other format suck as 'yyyyMMdd'...
// BE support other format such as 'yyyyMMdd'...
try {
return convertToDate(targetType);
} catch (AnalysisException e) {

View File

@ -26,11 +26,13 @@ import org.apache.doris.nereids.types.DateTimeType;
import org.apache.doris.nereids.types.coercion.DateLikeType;
import org.apache.doris.nereids.util.DateUtils;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
@ -40,6 +42,7 @@ import java.time.temporal.TemporalAccessor;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
/**
* date time literal.
@ -58,6 +61,8 @@ public class DateTimeLiteral extends DateLiteral {
private static final Logger LOG = LogManager.getLogger(DateTimeLiteral.class);
private static final Pattern HAS_OFFSET_PART = Pattern.compile("[\\+\\-]\\d{2}:\\d{2}");
protected long hour;
protected long minute;
protected long second;
@ -146,6 +151,26 @@ public class DateTimeLiteral extends DateLiteral {
protected void init(String s) throws AnalysisException {
try {
TemporalAccessor dateTime = null;
int offset = 0;
// parse timezone
if (haveTimeZoneOffset(s) || haveTimeZoneName(s)) {
String tzString = new String();
if (haveTimeZoneName(s)) { // GMT, UTC+8, Z[, CN, Asia/Shanghai]
int split = getTimeZoneSplitPos(s);
Preconditions.checkArgument(split > 0);
tzString = s.substring(split);
s = s.substring(0, split);
} else { // +04:30
Preconditions.checkArgument(s.charAt(s.length() - 6) == '-' || s.charAt(s.length() - 6) == '+');
tzString = s.substring(s.length() - 6);
s = s.substring(0, s.length() - 6);
}
ZoneId zone = ZoneId.of(tzString);
ZoneId dorisZone = DateUtils.getTimeZone();
offset = dorisZone.getRules().getOffset(java.time.Instant.now()).getTotalSeconds()
- zone.getRules().getOffset(java.time.Instant.now()).getTotalSeconds();
}
if (!s.contains("-")) {
// handle format like 20210106, but should not handle 2021-1-6
boolean parsed = false;
@ -231,6 +256,16 @@ public class DateTimeLiteral extends DateLiteral {
second = DateUtils.getOrDefault(dateTime, ChronoField.SECOND_OF_MINUTE);
microSecond = DateUtils.getOrDefault(dateTime, ChronoField.MICRO_OF_SECOND);
if (offset != 0) {
DateTimeLiteral result = (DateTimeLiteral) this.plusSeconds(offset);
this.second = result.second;
this.minute = result.minute;
this.hour = result.hour;
this.day = result.day;
this.month = result.month;
this.year = result.year;
}
} catch (Exception ex) {
throw new AnalysisException("datetime literal [" + s + "] is invalid");
}
@ -344,4 +379,27 @@ public class DateTimeLiteral extends DateLiteral {
: new DateTimeLiteral(dateTime.getYear(), dateTime.getMonthValue(), dateTime.getDayOfMonth(),
dateTime.getHour(), dateTime.getMinute(), dateTime.getSecond());
}
private static boolean haveTimeZoneOffset(String arg) {
Preconditions.checkArgument(arg.length() > 6);
return HAS_OFFSET_PART.matcher(arg.substring(arg.length() - 6)).matches();
}
private static boolean haveTimeZoneName(String arg) {
for (char ch : arg.toCharArray()) {
if (Character.isUpperCase(ch) && ch != 'T') {
return true;
}
}
return false;
}
private static int getTimeZoneSplitPos(String arg) {
int split = arg.length() - 1;
for (; !Character.isAlphabetic(arg.charAt(split)); split--) {
} // skip +8 of UTC+8
for (; split >= 0 && (Character.isUpperCase(arg.charAt(split)) || arg.charAt(split) == '/'); split--) {
}
return split + 1;
}
}