TimestampWithTimeZoneString.java
/*
* 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.apache.calcite.util;
import org.apache.calcite.avatica.util.DateTimeUtils;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;
import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.calcite.avatica.util.DateTimeUtils.DATE_FORMAT_STRING;
import static org.apache.calcite.avatica.util.DateTimeUtils.TIMESTAMP_FORMAT_STRING;
import static org.apache.calcite.util.DateTimeStringUtils.getDateFormatter;
import static org.apache.calcite.util.Static.RESOURCE;
import static java.lang.Math.floorMod;
import static java.util.Objects.requireNonNull;
/**
* Timestamp with time-zone literal.
*
* <p>Immutable, internally represented as a string (in ISO format),
* and can support unlimited precision (milliseconds, nanoseconds).
*/
public class TimestampWithTimeZoneString
implements Comparable<TimestampWithTimeZoneString> {
final TimestampString localDateTime;
final TimeZone timeZone;
final String v;
private final DateTimeUtils.PrecisionTime pt;
private static final ThreadLocal<@Nullable SimpleDateFormat> TIMESTAMP_FORMAT =
ThreadLocal.withInitial(() ->
getDateFormatter(TIMESTAMP_FORMAT_STRING));
/** Creates a TimestampWithTimeZoneString. */
public TimestampWithTimeZoneString(TimestampString localDateTime, TimeZone timeZone) {
this.localDateTime = localDateTime;
this.timeZone = timeZone;
this.v = localDateTime.toString() + " " + timeZone.getID();
this.pt = parseDateTime(localDateTime.toString(), this.timeZone);
}
/** Creates a TimestampWithTimeZoneString. */
public TimestampWithTimeZoneString(String v) {
// search next space after "yyyy-MM-dd "
int pos = v.indexOf(' ', DATE_FORMAT_STRING.length() + 1);
if (pos == -1) {
throw RESOURCE.illegalLiteral("TIMESTAMP WITH LOCAL TIME ZONE", v,
RESOURCE.badFormat(TIMESTAMP_FORMAT_STRING).str()).ex();
}
String tsStr = v.substring(0, pos);
this.localDateTime = new TimestampString(tsStr);
String timeZoneString = v.substring(v.indexOf(' ', DATE_FORMAT_STRING.length() + 1) + 1);
checkArgument(DateTimeStringUtils.isValidTimeZone(timeZoneString));
this.timeZone = TimeZone.getTimeZone(timeZoneString);
this.v = v;
this.pt = parseDateTime(tsStr, this.timeZone);
}
/** Creates a TimestampWithTimeZoneString for year, month, day, hour, minute,
* second, millisecond values in the given time-zone. */
public TimestampWithTimeZoneString(int year, int month, int day, int h, int m, int s,
String timeZone) {
this(DateTimeStringUtils.ymdhms(new StringBuilder(), year, month, day, h, m, s).toString()
+ " " + timeZone);
}
/** Sets the fraction field of a {@code TimestampWithTimeZoneString} to a given number
* of milliseconds. Nukes the value set via {@link #withNanos}.
*
* <p>For example,
* {@code new TimestampWithTimeZoneString(1970, 1, 1, 2, 3, 4, "GMT").withMillis(56)}
* yields {@code TIMESTAMP WITH LOCAL TIME ZONE '1970-01-01 02:03:04.056 GMT'}. */
public TimestampWithTimeZoneString withMillis(int millis) {
checkArgument(millis >= 0 && millis < 1000);
return withFraction(DateTimeStringUtils.pad(3, millis));
}
/** Sets the fraction field of a {@code TimestampWithTimeZoneString} to a given number
* of nanoseconds. Nukes the value set via {@link #withMillis(int)}.
*
* <p>For example,
* {@code new TimestampWithTimeZoneString(1970, 1, 1, 2, 3, 4, "GMT").withNanos(56789)}
* yields {@code TIMESTAMP WITH LOCAL TIME ZONE '1970-01-01 02:03:04.000056789 GMT'}. */
public TimestampWithTimeZoneString withNanos(int nanos) {
checkArgument(nanos >= 0 && nanos < 1000000000);
return withFraction(DateTimeStringUtils.pad(9, nanos));
}
/** Sets the fraction field of a {@code TimestampString}.
* The precision is determined by the number of leading zeros.
* Trailing zeros are stripped.
*
* <p>For example, {@code
* new TimestampWithTimeZoneString(1970, 1, 1, 2, 3, 4, "GMT").withFraction("00506000")}
* yields {@code TIMESTAMP WITH LOCAL TIME ZONE '1970-01-01 02:03:04.00506 GMT'}. */
public TimestampWithTimeZoneString withFraction(String fraction) {
return new TimestampWithTimeZoneString(
localDateTime.withFraction(fraction), timeZone);
}
/** Creates a TimestampWithTimeZoneString from a Calendar. */
public static TimestampWithTimeZoneString fromCalendarFields(Calendar calendar) {
TimestampString ts = TimestampString.fromCalendarFields(calendar);
return new TimestampWithTimeZoneString(ts, calendar.getTimeZone());
}
public TimestampWithTimeZoneString withTimeZone(TimeZone timeZone) {
if (this.timeZone.equals(timeZone)) {
return this;
}
String fraction = pt.getFraction();
pt.getCalendar().setTimeZone(timeZone);
if (!fraction.isEmpty()) {
return new TimestampWithTimeZoneString(
pt.getCalendar().get(Calendar.YEAR),
pt.getCalendar().get(Calendar.MONTH) + 1,
pt.getCalendar().get(Calendar.DAY_OF_MONTH),
pt.getCalendar().get(Calendar.HOUR_OF_DAY),
pt.getCalendar().get(Calendar.MINUTE),
pt.getCalendar().get(Calendar.SECOND),
timeZone.getID())
.withFraction(fraction);
}
return new TimestampWithTimeZoneString(
pt.getCalendar().get(Calendar.YEAR),
pt.getCalendar().get(Calendar.MONTH) + 1,
pt.getCalendar().get(Calendar.DAY_OF_MONTH),
pt.getCalendar().get(Calendar.HOUR_OF_DAY),
pt.getCalendar().get(Calendar.MINUTE),
pt.getCalendar().get(Calendar.SECOND),
timeZone.getID());
}
@Override public String toString() {
return v;
}
@Override public boolean equals(@Nullable Object o) {
// The value is in canonical form (no trailing zeros).
return o == this
|| o instanceof TimestampWithTimeZoneString
&& ((TimestampWithTimeZoneString) o).v.equals(v);
}
@Override public int hashCode() {
return v.hashCode();
}
@Override public int compareTo(TimestampWithTimeZoneString o) {
return v.compareTo(o.v);
}
public TimestampWithTimeZoneString round(int precision) {
checkArgument(precision >= 0);
return new TimestampWithTimeZoneString(
localDateTime.round(precision), timeZone);
}
/** Creates a TimestampWithTimeZoneString that is a given number of milliseconds since
* the epoch UTC. */
public static TimestampWithTimeZoneString fromMillisSinceEpoch(long millis) {
return new TimestampWithTimeZoneString(
DateTimeUtils.unixTimestampToString(millis) + " " + DateTimeUtils.UTC_ZONE.getID())
.withMillis((int) floorMod(millis, 1000L));
}
/** Converts this TimestampWithTimeZoneString to a string, truncated or padded with
* zeros to a given precision. */
public String toString(int precision) {
checkArgument(precision >= 0);
return localDateTime.toString(precision) + " " + timeZone.getID();
}
public DateString getLocalDateString() {
return new DateString(localDateTime.toString().substring(0, 10));
}
public TimeString getLocalTimeString() {
return new TimeString(localDateTime.toString().substring(11));
}
public TimestampString getLocalTimestampString() {
return localDateTime;
}
public TimeZone getTimeZone() {
return timeZone;
}
private static DateTimeUtils.PrecisionTime parseDateTime(String tsStr, TimeZone timeZone) {
DateTimeUtils.PrecisionTime pt =
DateTimeUtils.parsePrecisionDateTimeLiteral(tsStr, requireNonNull(TIMESTAMP_FORMAT.get()),
timeZone, -1);
if (pt == null) {
throw RESOURCE.illegalLiteral("TIMESTAMP WITH LOCAL TIME ZONE", tsStr,
RESOURCE.badFormat(TIMESTAMP_FORMAT_STRING).str()).ex();
}
return pt;
}
}