
org.dinky.shaded.paimon.utils.DateTimeUtils Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 org.dinky.shaded.paimon.utils;
import org.dinky.shaded.paimon.data.Timestamp;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.TemporalAccessor;
import java.util.TimeZone;
import static java.time.temporal.ChronoField.DAY_OF_MONTH;
import static java.time.temporal.ChronoField.HOUR_OF_DAY;
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
import static java.time.temporal.ChronoField.NANO_OF_SECOND;
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
import static java.time.temporal.ChronoField.YEAR;
/** Utils for date time. */
public class DateTimeUtils {
/** The julian date of the epoch, 1970-01-01. */
public static final int EPOCH_JULIAN = 2440588;
/** The number of milliseconds in a second. */
private static final long MILLIS_PER_SECOND = 1000L;
/** The number of milliseconds in a minute. */
private static final long MILLIS_PER_MINUTE = 60000L;
/** The number of milliseconds in an hour. */
private static final long MILLIS_PER_HOUR = 3600000L; // = 60 * 60 * 1000
/**
* The number of milliseconds in a day.
*
* This is the modulo 'mask' used when converting TIMESTAMP values to DATE and TIME values.
*/
public static final long MILLIS_PER_DAY = 86400000L; // = 24 * 60 * 60 * 1000
/** The UTC time zone. */
public static final TimeZone UTC_ZONE = TimeZone.getTimeZone("UTC");
/** The local time zone. */
public static final TimeZone LOCAL_TZ = TimeZone.getDefault();
private static final DateTimeFormatter DEFAULT_TIMESTAMP_FORMATTER =
new DateTimeFormatterBuilder()
.appendPattern("yyyy-[MM][M]-[dd][d]")
.optionalStart()
.appendPattern(" [HH][H]:[mm][m]:[ss][s]")
.appendFraction(NANO_OF_SECOND, 0, 9, true)
.optionalEnd()
.toFormatter();
/**
* Converts the internal representation of a SQL DATE (int) to the Java type used for UDF
* parameters ({@link java.sql.Date}).
*/
public static java.sql.Date toSQLDate(int v) {
// note that, in this case, can't handle Daylight Saving Time
final long t = v * MILLIS_PER_DAY;
return new java.sql.Date(t - LOCAL_TZ.getOffset(t));
}
/**
* Converts the internal representation of a SQL TIME (int) to the Java type used for UDF
* parameters ({@link java.sql.Time}).
*/
public static java.sql.Time toSQLTime(int v) {
// note that, in this case, can't handle Daylight Saving Time
return new java.sql.Time(v - LOCAL_TZ.getOffset(v));
}
/**
* Converts the Java type used for UDF parameters of SQL DATE type ({@link java.sql.Date}) to
* internal representation (int).
*/
public static int toInternal(java.sql.Date date) {
long ts = date.getTime() + LOCAL_TZ.getOffset(date.getTime());
return (int) (ts / MILLIS_PER_DAY);
}
/**
* Converts the Java type used for UDF parameters of SQL TIME type ({@link java.sql.Time}) to
* internal representation (int).
*
*
Converse of {@link #toSQLTime(int)}.
*/
public static int toInternal(java.sql.Time time) {
long ts = time.getTime() + LOCAL_TZ.getOffset(time.getTime());
return (int) (ts % MILLIS_PER_DAY);
}
public static Timestamp toInternal(long millis, int nanos) {
return Timestamp.fromEpochMillis(millis + LOCAL_TZ.getOffset(millis), nanos);
}
public static int toInternal(LocalDate date) {
return ymdToUnixDate(date.getYear(), date.getMonthValue(), date.getDayOfMonth());
}
public static int toInternal(LocalTime time) {
return time.getHour() * (int) MILLIS_PER_HOUR
+ time.getMinute() * (int) MILLIS_PER_MINUTE
+ time.getSecond() * (int) MILLIS_PER_SECOND
+ time.getNano() / 1000_000;
}
/**
* Format a {@link LocalDateTime} to yyyy-MM-dd HH:mm:ss[.nano] string.
*
* @param precision how many digits of nanoseconds to be retained
*/
public static String formatLocalDateTime(LocalDateTime localDateTime, int precision) {
// nanosecond is range in 0 ~ 999_999_999
Preconditions.checkArgument(
precision >= 0 && precision <= 9, "precision should be in range 0 ~ 9.");
// format year to second part
StringBuilder ymdhms =
ymdhms(
new StringBuilder(),
localDateTime.getYear(),
localDateTime.getMonthValue(),
localDateTime.getDayOfMonth(),
localDateTime.getHour(),
localDateTime.getMinute(),
localDateTime.getSecond());
// format nanosecond part
StringBuilder fraction = new StringBuilder(Long.toString(localDateTime.getNano()));
while (fraction.length() < 9) {
fraction.insert(0, "0");
}
String nano = fraction.substring(0, precision);
if (nano.length() > 0) {
ymdhms.append(".").append(nano);
}
return ymdhms.toString();
}
// --------------------------------------------------------------------------------------------
// Java 8 time conversion
// --------------------------------------------------------------------------------------------
public static LocalDate toLocalDate(int date) {
return julianToLocalDate(date + EPOCH_JULIAN);
}
private static LocalDate julianToLocalDate(int julian) {
// this shifts the epoch back to astronomical year -4800 instead of the
// start of the Christian era in year AD 1 of the proleptic Gregorian
// calendar.
int j = julian + 32044;
int g = j / 146097;
int dg = j % 146097;
int c = (dg / 36524 + 1) * 3 / 4;
int dc = dg - c * 36524;
int b = dc / 1461;
int db = dc % 1461;
int a = (db / 365 + 1) * 3 / 4;
int da = db - a * 365;
// integer number of full years elapsed since March 1, 4801 BC
int y = g * 400 + c * 100 + b * 4 + a;
// integer number of full months elapsed since the last March 1
int m = (da * 5 + 308) / 153 - 2;
// number of days elapsed since day 1 of the month
int d = da - (m + 4) * 153 / 5 + 122;
int year = y - 4800 + (m + 2) / 12;
int month = (m + 2) % 12 + 1;
int day = d + 1;
return LocalDate.of(year, month, day);
}
public static LocalTime toLocalTime(int time) {
int h = time / 3600000;
int time2 = time % 3600000;
int m = time2 / 60000;
int time3 = time2 % 60000;
int s = time3 / 1000;
int ms = time3 % 1000;
return LocalTime.of(h, m, s, ms * 1000_000);
}
public static Integer parseDate(String s) {
// allow timestamp str to date, e.g. 2017-12-12 09:30:00.0
int ws1 = s.indexOf(" ");
if (ws1 > 0) {
s = s.substring(0, ws1);
}
int hyphen1 = s.indexOf('-');
int y;
int m;
int d;
if (hyphen1 < 0) {
if (!isInteger(s.trim())) {
return null;
}
y = Integer.parseInt(s.trim());
m = 1;
d = 1;
} else {
if (!isInteger(s.substring(0, hyphen1).trim())) {
return null;
}
y = Integer.parseInt(s.substring(0, hyphen1).trim());
final int hyphen2 = s.indexOf('-', hyphen1 + 1);
if (hyphen2 < 0) {
if (!isInteger(s.substring(hyphen1 + 1).trim())) {
return null;
}
m = Integer.parseInt(s.substring(hyphen1 + 1).trim());
d = 1;
} else {
if (!isInteger(s.substring(hyphen1 + 1, hyphen2).trim())) {
return null;
}
m = Integer.parseInt(s.substring(hyphen1 + 1, hyphen2).trim());
if (!isInteger(s.substring(hyphen2 + 1).trim())) {
return null;
}
d = Integer.parseInt(s.substring(hyphen2 + 1).trim());
}
}
if (!isIllegalDate(y, m, d)) {
return null;
}
return ymdToUnixDate(y, m, d);
}
public static Integer parseTime(String v) {
final int start = 0;
final int colon1 = v.indexOf(':', start);
// timezone hh:mm:ss[.ssssss][[+|-]hh:mm:ss]
// refer https://www.w3.org/TR/NOTE-datetime
int timezoneHour;
int timezoneMinute;
int hour;
int minute;
int second;
int milli;
int operator = -1;
int end = v.length();
int timezone = v.indexOf('-', start);
if (timezone < 0) {
timezone = v.indexOf('+', start);
operator = 1;
}
if (timezone < 0) {
timezoneHour = 0;
timezoneMinute = 0;
} else {
end = timezone;
final int colon3 = v.indexOf(':', timezone);
if (colon3 < 0) {
if (!isInteger(v.substring(timezone + 1).trim())) {
return null;
}
timezoneHour = Integer.parseInt(v.substring(timezone + 1).trim());
timezoneMinute = 0;
} else {
if (!isInteger(v.substring(timezone + 1, colon3).trim())) {
return null;
}
timezoneHour = Integer.parseInt(v.substring(timezone + 1, colon3).trim());
if (!isInteger(v.substring(colon3 + 1).trim())) {
return null;
}
timezoneMinute = Integer.parseInt(v.substring(colon3 + 1).trim());
}
}
if (colon1 < 0) {
if (!isInteger(v.substring(start, end).trim())) {
return null;
}
hour = Integer.parseInt(v.substring(start, end).trim());
minute = 0;
second = 0;
milli = 0;
} else {
if (!isInteger(v.substring(start, colon1).trim())) {
return null;
}
hour = Integer.parseInt(v.substring(start, colon1).trim());
final int colon2 = v.indexOf(':', colon1 + 1);
if (colon2 < 0) {
if (!isInteger(v.substring(colon1 + 1, end).trim())) {
return null;
}
minute = Integer.parseInt(v.substring(colon1 + 1, end).trim());
second = 0;
milli = 0;
} else {
if (!isInteger(v.substring(colon1 + 1, colon2).trim())) {
return null;
}
minute = Integer.parseInt(v.substring(colon1 + 1, colon2).trim());
int dot = v.indexOf('.', colon2);
if (dot < 0) {
if (!isInteger(v.substring(colon2 + 1, end).trim())) {
return null;
}
second = Integer.parseInt(v.substring(colon2 + 1, end).trim());
milli = 0;
} else {
if (!isInteger(v.substring(colon2 + 1, dot).trim())) {
return null;
}
second = Integer.parseInt(v.substring(colon2 + 1, dot).trim());
milli = parseFraction(v.substring(dot + 1, end).trim());
}
}
}
hour += operator * timezoneHour;
minute += operator * timezoneMinute;
return hour * (int) MILLIS_PER_HOUR
+ minute * (int) MILLIS_PER_MINUTE
+ second * (int) MILLIS_PER_SECOND
+ milli;
}
private static boolean isInteger(String s) {
boolean isInt = s.length() > 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) < '0' || s.charAt(i) > '9') {
isInt = false;
break;
}
}
return isInt;
}
private static boolean isIllegalDate(int y, int m, int d) {
int[] monthOf31Days = new int[] {1, 3, 5, 7, 8, 10, 12};
if (y < 0 || y > 9999 || m < 1 || m > 12 || d < 1 || d > 31) {
return false;
}
if (m == 2 && d > 28) {
if (!(isLeapYear(y) && d == 29)) {
return false;
}
}
if (d == 31) {
for (int i : monthOf31Days) {
if (i == m) {
return true;
}
}
return false;
}
return true;
}
/**
* Parses a fraction, multiplying the first character by {@code multiplier}, the second
* character by {@code multiplier / 10}, the third character by {@code multiplier / 100}, and so
* forth.
*
*
For example, {@code parseFraction("1234", 100)} yields {@code 123}.
*/
private static int parseFraction(String v) {
int multiplier = 100;
int r = 0;
for (int i = 0; i < v.length(); i++) {
char c = v.charAt(i);
int x = c < '0' || c > '9' ? 0 : (c - '0');
r += multiplier * x;
if (multiplier < 10) {
// We're at the last digit. Check for rounding.
if (i + 1 < v.length() && v.charAt(i + 1) >= '5') {
++r;
}
break;
}
multiplier /= 10;
}
return r;
}
// --------------------------------------------------------------------------------------------
// Format
// --------------------------------------------------------------------------------------------
private static String formatTimestamp(Timestamp ts, int precision) {
LocalDateTime ldt = ts.toLocalDateTime();
String fraction = pad(9, ldt.getNano());
while (fraction.length() > precision && fraction.endsWith("0")) {
fraction = fraction.substring(0, fraction.length() - 1);
}
StringBuilder ymdhms =
ymdhms(
new StringBuilder(),
ldt.getYear(),
ldt.getMonthValue(),
ldt.getDayOfMonth(),
ldt.getHour(),
ldt.getMinute(),
ldt.getSecond());
if (fraction.length() > 0) {
ymdhms.append(".").append(fraction);
}
return ymdhms.toString();
}
public static String formatTimestamp(Timestamp ts, TimeZone tz, int precision) {
return formatTimestamp(timestampWithLocalZoneToTimestamp(ts, tz), precision);
}
public static String formatTimestampMillis(int time, int precision) {
final StringBuilder buf = new StringBuilder(8 + (precision > 0 ? precision + 1 : 0));
formatTimestampMillis(buf, time, precision);
return buf.toString();
}
private static void formatTimestampMillis(StringBuilder buf, int time, int precision) {
// we copy this method from Calcite DateTimeUtils but add the following changes
// time may be negative which means time milli seconds before 00:00:00
// this maybe a bug in calcite avatica
while (time < 0) {
time += MILLIS_PER_DAY;
}
int h = time / 3600000;
int time2 = time % 3600000;
int m = time2 / 60000;
int time3 = time2 % 60000;
int s = time3 / 1000;
int ms = time3 % 1000;
int2(buf, h);
buf.append(':');
int2(buf, m);
buf.append(':');
int2(buf, s);
if (precision > 0) {
buf.append('.');
while (precision > 0) {
buf.append((char) ('0' + (ms / 100)));
ms = ms % 100;
ms = ms * 10;
// keep consistent with Timestamp.toString()
if (ms == 0) {
break;
}
--precision;
}
}
}
/** Helper for CAST({date} AS VARCHAR(n)). */
public static String formatDate(int date) {
final StringBuilder buf = new StringBuilder(10);
formatDate(buf, date);
return buf.toString();
}
private static void formatDate(StringBuilder buf, int date) {
julianToString(buf, date + EPOCH_JULIAN);
}
private static void julianToString(StringBuilder buf, int julian) {
// this shifts the epoch back to astronomical year -4800 instead of the
// start of the Christian era in year AD 1 of the proleptic Gregorian
// calendar.
int j = julian + 32044;
int g = j / 146097;
int dg = j % 146097;
int c = (dg / 36524 + 1) * 3 / 4;
int dc = dg - c * 36524;
int b = dc / 1461;
int db = dc % 1461;
int a = (db / 365 + 1) * 3 / 4;
int da = db - a * 365;
// integer number of full years elapsed since March 1, 4801 BC
int y = g * 400 + c * 100 + b * 4 + a;
// integer number of full months elapsed since the last March 1
int m = (da * 5 + 308) / 153 - 2;
// number of days elapsed since day 1 of the month
int d = da - (m + 4) * 153 / 5 + 122;
int year = y - 4800 + (m + 2) / 12;
int month = (m + 2) % 12 + 1;
int day = d + 1;
int4(buf, year);
buf.append('-');
int2(buf, month);
buf.append('-');
int2(buf, day);
}
private static boolean isLeapYear(int s) {
return s % 400 == 0 || (s % 4 == 0 && s % 100 != 0);
}
private static int ymdToUnixDate(int year, int month, int day) {
final int julian = ymdToJulian(year, month, day);
return julian - EPOCH_JULIAN;
}
private static int ymdToJulian(int year, int month, int day) {
int a = (14 - month) / 12;
int y = year + 4800 - a;
int m = month + 12 * a - 3;
return day + (153 * m + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045;
}
public static Timestamp parseTimestampData(String dateStr, int precision)
throws DateTimeException {
return Timestamp.fromLocalDateTime(
fromTemporalAccessor(DEFAULT_TIMESTAMP_FORMATTER.parse(dateStr), precision));
}
public static Timestamp parseTimestampData(String dateStr, int precision, TimeZone timeZone)
throws DateTimeException {
return Timestamp.fromInstant(
fromTemporalAccessor(DEFAULT_TIMESTAMP_FORMATTER.parse(dateStr), precision)
.atZone(timeZone.toZoneId())
.toInstant());
}
public static LocalDateTime toLocalDateTime(long timeMills) {
return toLocalDateTime(timeMills, ZoneId.systemDefault());
}
public static LocalDateTime toLocalDateTime(long timeMills, ZoneId zoneId) {
return Instant.ofEpochMilli(timeMills).atZone(zoneId).toLocalDateTime();
}
public static LocalDateTime toLocalDateTime(String dateStr, int precision) {
return fromTemporalAccessor(DEFAULT_TIMESTAMP_FORMATTER.parse(dateStr), precision);
}
/**
* This is similar to {@link LocalDateTime#from(TemporalAccessor)}, but it's less strict and
* introduces default values.
*/
private static LocalDateTime fromTemporalAccessor(TemporalAccessor accessor, int precision) {
// complement year with 1970
int year = accessor.isSupported(YEAR) ? accessor.get(YEAR) : 1970;
// complement month with 1
int month = accessor.isSupported(MONTH_OF_YEAR) ? accessor.get(MONTH_OF_YEAR) : 1;
// complement day with 1
int day = accessor.isSupported(DAY_OF_MONTH) ? accessor.get(DAY_OF_MONTH) : 1;
// complement hour with 0
int hour = accessor.isSupported(HOUR_OF_DAY) ? accessor.get(HOUR_OF_DAY) : 0;
// complement minute with 0
int minute = accessor.isSupported(MINUTE_OF_HOUR) ? accessor.get(MINUTE_OF_HOUR) : 0;
// complement second with 0
int second = accessor.isSupported(SECOND_OF_MINUTE) ? accessor.get(SECOND_OF_MINUTE) : 0;
// complement nano_of_second with 0
int nanoOfSecond = accessor.isSupported(NANO_OF_SECOND) ? accessor.get(NANO_OF_SECOND) : 0;
if (precision == 0) {
nanoOfSecond = 0;
} else if (precision != 9) {
nanoOfSecond = (int) floor(nanoOfSecond, powerX(10, 9 - precision));
}
return LocalDateTime.of(year, month, day, hour, minute, second, nanoOfSecond);
}
private static long floor(long a, long b) {
long r = a % b;
if (r < 0) {
return a - r - b;
} else {
return a - r;
}
}
private static long powerX(long a, long b) {
long x = 1;
while (b > 0) {
x *= a;
--b;
}
return x;
}
// --------------------------------------------------------------------------------------------
// UNIX TIME
// --------------------------------------------------------------------------------------------
/** Returns the value of the timestamp to seconds since '1970-01-01 00:00:00' UTC. */
public static long unixTimestamp(long ts) {
return ts / 1000;
}
// --------------------------------------------------------------------------------------------
// TIMESTAMP to TIMESTAMP_LTZ conversions
// --------------------------------------------------------------------------------------------
public static Timestamp timestampToTimestampWithLocalZone(Timestamp ts, TimeZone tz) {
return Timestamp.fromInstant(ts.toLocalDateTime().atZone(tz.toZoneId()).toInstant());
}
public static Timestamp timestampWithLocalZoneToTimestamp(Timestamp ts, TimeZone tz) {
return Timestamp.fromLocalDateTime(LocalDateTime.ofInstant(ts.toInstant(), tz.toZoneId()));
}
public static int timestampWithLocalZoneToDate(Timestamp ts, TimeZone tz) {
return toInternal(
LocalDateTime.ofInstant(Instant.ofEpochMilli(ts.getMillisecond()), tz.toZoneId())
.toLocalDate());
}
public static int timestampWithLocalZoneToTime(Timestamp ts, TimeZone tz) {
return toInternal(
LocalDateTime.ofInstant(Instant.ofEpochMilli(ts.getMillisecond()), tz.toZoneId())
.toLocalTime());
}
public static Timestamp dateToTimestampWithLocalZone(int date, TimeZone tz) {
return Timestamp.fromInstant(
LocalDateTime.of(toLocalDate(date), LocalTime.MIDNIGHT)
.atZone(tz.toZoneId())
.toInstant());
}
public static Timestamp timeToTimestampWithLocalZone(int time, TimeZone tz) {
return Timestamp.fromInstant(toLocalDateTime(time).atZone(tz.toZoneId()).toInstant());
}
public static Timestamp truncate(Timestamp ts, int precision) {
String fraction = Integer.toString(ts.toLocalDateTime().getNano());
if (fraction.length() <= precision) {
return ts;
} else {
// need to truncate
if (precision <= 3) {
return Timestamp.fromEpochMillis(
zeroLastDigits(ts.getMillisecond(), 3 - precision));
} else {
return Timestamp.fromEpochMillis(
ts.getMillisecond(),
(int) zeroLastDigits(ts.getNanoOfMillisecond(), 9 - precision));
}
}
}
private static long zeroLastDigits(long l, int n) {
long tenToTheN = (long) Math.pow(10, n);
return (l / tenToTheN) * tenToTheN;
}
private static String pad(int length, long v) {
StringBuilder s = new StringBuilder(Long.toString(v));
while (s.length() < length) {
s.insert(0, "0");
}
return s.toString();
}
/** Appends year-month-day and hour:minute:second to a buffer; assumes they are valid. */
private static StringBuilder ymdhms(
StringBuilder b, int year, int month, int day, int h, int m, int s) {
ymd(b, year, month, day);
b.append(' ');
return hms(b, h, m, s);
}
/** Appends year-month-day to a buffer; assumes they are valid. */
private static StringBuilder ymd(StringBuilder b, int year, int month, int day) {
int4(b, year);
b.append('-');
int2(b, month);
b.append('-');
return int2(b, day);
}
/** Appends hour:minute:second to a buffer; assumes they are valid. */
private static StringBuilder hms(StringBuilder b, int h, int m, int s) {
int2(b, h);
b.append(':');
int2(b, m);
b.append(':');
return int2(b, s);
}
private static StringBuilder int4(StringBuilder buf, int i) {
buf.append((char) ('0' + (i / 1000) % 10));
buf.append((char) ('0' + (i / 100) % 10));
buf.append((char) ('0' + (i / 10) % 10));
return buf.append((char) ('0' + i % 10));
}
private static StringBuilder int2(StringBuilder buf, int i) {
buf.append((char) ('0' + (i / 10) % 10));
return buf.append((char) ('0' + i % 10));
}
}