package org.postgresql.jdbc;
import org.postgresql.PGStatement;
import org.postgresql.core.JavaVersion;
import org.postgresql.core.Oid;
import org.postgresql.core.Provider;
import org.postgresql.util.ByteConverter;
import org.postgresql.util.GT;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import java.lang.reflect.Field;
import java.sql.Date;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.chrono.IsoEra;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
public class TimestampUtils {
private static final int ONEDAY = 24 * 3600 * 1000;
private static final char[] ZEROS = {'0', '0', '0', '0', '0', '0', '0', '0', '0'};
private static final char[][] NUMBERS;
private static final HashMap<String, TimeZone> GMT_ZONES = new HashMap<String, TimeZone>();
private static final int MAX_NANOS_BEFORE_WRAP_ON_ROUND = 999999500;
private static final Duration ONE_MICROSECOND = Duration.ofNanos(1000);
private static final LocalTime MAX_TIME = LocalTime.MAX.minus(Duration.ofMillis(500));
private static final OffsetDateTime MAX_OFFSET_DATETIME = OffsetDateTime.MAX.minus(Duration.ofMillis(500));
private static final LocalDateTime MAX_LOCAL_DATETIME = LocalDateTime.MAX.minus(Duration.ofMillis(500));
private static final Field DEFAULT_TIME_ZONE_FIELD;
private TimeZone prevDefaultZoneFieldValue;
private TimeZone defaultTimeZoneCache;
static {
NUMBERS = new char[64][];
for (int i = 0; i < NUMBERS.length; i++) {
NUMBERS[i] = ((i < 10 ? "0" : "") + Integer.toString(i)).toCharArray();
}
for (int i = -12; i <= 14; i++) {
TimeZone timeZone;
String pgZoneName;
if (i == 0) {
timeZone = TimeZone.getTimeZone("GMT");
pgZoneName = "GMT";
} else {
timeZone = TimeZone.getTimeZone("GMT" + (i <= 0 ? "+" : "-") + Math.abs(i));
pgZoneName = "GMT" + (i >= 0 ? "+" : "-");
}
if (i == 0) {
GMT_ZONES.put(pgZoneName, timeZone);
continue;
}
GMT_ZONES.put(pgZoneName + Math.abs(i), timeZone);
GMT_ZONES.put(pgZoneName + new String(NUMBERS[Math.abs(i)]), timeZone);
}
Field tzField;
try {
tzField = null;
if (JavaVersion.getRuntimeVersion().compareTo(JavaVersion.v1_8) <= 0) {
tzField = TimeZone.class.getDeclaredField("defaultTimeZone");
tzField.setAccessible(true);
TimeZone defaultTz = TimeZone.getDefault();
Object tzFromField = tzField.get(null);
if (defaultTz == null || !defaultTz.equals(tzFromField)) {
tzField = null;
}
}
} catch (Exception e) {
tzField = null;
}
DEFAULT_TIME_ZONE_FIELD = tzField;
}
private final StringBuilder sbuf = new StringBuilder();
private final Calendar calendarWithUserTz = new GregorianCalendar();
private final TimeZone utcTz = TimeZone.getTimeZone("UTC");
private Calendar calCache;
private int calCacheZone;
private final boolean usesDouble;
private final Provider<TimeZone> timeZoneProvider;
TimestampUtils(boolean usesDouble, Provider<TimeZone> timeZoneProvider) {
this.usesDouble = usesDouble;
this.timeZoneProvider = timeZoneProvider;
}
private Calendar getCalendar(int sign, int hr, int min, int sec) {
int rawOffset = sign * (((hr * 60 + min) * 60 + sec) * 1000);
if (calCache != null && calCacheZone == rawOffset) {
return calCache;
}
StringBuilder zoneID = new StringBuilder("GMT");
zoneID.append(sign < 0 ? '-' : '+');
if (hr < 10) {
zoneID.append('0');
}
zoneID.append(hr);
if (min < 10) {
zoneID.append('0');
}
zoneID.append(min);
if (sec < 10) {
zoneID.append('0');
}
zoneID.append(sec);
TimeZone syntheticTZ = new SimpleTimeZone(rawOffset, zoneID.toString());
calCache = new GregorianCalendar(syntheticTZ);
calCacheZone = rawOffset;
return calCache;
}
private static class ParsedTimestamp {
boolean hasDate = false;
int era = GregorianCalendar.AD;
int year = 1970;
int month = 1;
boolean hasTime = false;
int day = 1;
int hour = 0;
int minute = 0;
int second = 0;
int nanos = 0;
Calendar tz = null;
}
private static class ParsedBinaryTimestamp {
Infinity infinity = null;
long millis = 0;
int nanos = 0;
}
enum Infinity {
POSITIVE,
NEGATIVE;
}
private ParsedTimestamp parseBackendTimestamp(String str) throws SQLException {
char[] s = str.toCharArray();
int slen = s.length;
ParsedTimestamp result = new ParsedTimestamp();
try {
int start = skipWhitespace(s, 0);
int end = firstNonDigit(s, start);
int num;
char sep;
if (charAt(s, end) == '-') {
result.hasDate = true;
result.year = number(s, start, end);
start = end + 1;
end = firstNonDigit(s, start);
result.month = number(s, start, end);
sep = charAt(s, end);
if (sep != '-') {
throw new NumberFormatException("Expected date to be dash-separated, got '" + sep + "'");
}
start = end + 1;
end = firstNonDigit(s, start);
result.day = number(s, start, end);
start = skipWhitespace(s, end);
}
if (Character.isDigit(charAt(s, start))) {
result.hasTime = true;
end = firstNonDigit(s, start);
result.hour = number(s, start, end);
sep = charAt(s, end);
if (sep != ':') {
throw new NumberFormatException("Expected time to be colon-separated, got '" + sep + "'");
}
start = end + 1;
end = firstNonDigit(s, start);
result.minute = number(s, start, end);
sep = charAt(s, end);
if (sep != ':') {
throw new NumberFormatException("Expected time to be colon-separated, got '" + sep + "'");
}
start = end + 1;
end = firstNonDigit(s, start);
result.second = number(s, start, end);
start = end;
if (charAt(s, start) == '.') {
end = firstNonDigit(s, start + 1);
num = number(s, start + 1, end);
for (int numlength = (end - (start + 1)); numlength < 9; ++numlength) {
num *= 10;
}
result.nanos = num;
start = end;
}
start = skipWhitespace(s, start);
}
sep = charAt(s, start);
if (sep == '-' || sep == '+') {
int tzsign = (sep == '-') ? -1 : 1;
int tzhr;
int tzmin;
int tzsec;
end = firstNonDigit(s, start + 1);
tzhr = number(s, start + 1, end);
start = end;
sep = charAt(s, start);
if (sep == ':') {
end = firstNonDigit(s, start + 1);
tzmin = number(s, start + 1, end);
start = end;
} else {
tzmin = 0;
}
tzsec = 0;
sep = charAt(s, start);
if (sep == ':') {
end = firstNonDigit(s, start + 1);
tzsec = number(s, start + 1, end);
start = end;
}
result.tz = getCalendar(tzsign, tzhr, tzmin, tzsec);
start = skipWhitespace(s, start);
}
if (result.hasDate && start < slen) {
String eraString = new String(s, start, slen - start);
if (eraString.startsWith("AD")) {
result.era = GregorianCalendar.AD;
start += 2;
} else if (eraString.startsWith("BC")) {
result.era = GregorianCalendar.BC;
start += 2;
}
}
if (start < slen) {
throw new NumberFormatException(
"Trailing junk on timestamp: '" + new String(s, start, slen - start) + "'");
}
if (!result.hasTime && !result.hasDate) {
throw new NumberFormatException("Timestamp has neither date nor time");
}
} catch (NumberFormatException nfe) {
throw new PSQLException(
GT.tr("Bad value for type timestamp/date/time: {1}", str),
PSQLState.BAD_DATETIME_FORMAT, nfe);
}
return result;
}
public synchronized Timestamp toTimestamp(Calendar cal, String s) throws SQLException {
if (s == null) {
return null;
}
int slen = s.length();
if (slen == 8 && s.equals("infinity")) {
return new Timestamp(PGStatement.DATE_POSITIVE_INFINITY);
}
if (slen == 9 && s.equals("-infinity")) {
return new Timestamp(PGStatement.DATE_NEGATIVE_INFINITY);
}
ParsedTimestamp ts = parseBackendTimestamp(s);
Calendar useCal = ts.tz != null ? ts.tz : setupCalendar(cal);
useCal.set(Calendar.ERA, ts.era);
useCal.set(Calendar.YEAR, ts.year);
useCal.set(Calendar.MONTH, ts.month - 1);
useCal.set(Calendar.DAY_OF_MONTH, ts.day);
useCal.set(Calendar.HOUR_OF_DAY, ts.hour);
useCal.set(Calendar.MINUTE, ts.minute);
useCal.set(Calendar.SECOND, ts.second);
useCal.set(Calendar.MILLISECOND, 0);
Timestamp result = new Timestamp(useCal.getTimeInMillis());
result.setNanos(ts.nanos);
return result;
}
public LocalTime toLocalTime(String s) throws SQLException {
if (s == null) {
return null;
}
if (s.equals("24:00:00")) {
return LocalTime.MAX;
}
try {
return LocalTime.parse(s);
} catch (DateTimeParseException nfe) {
throw new PSQLException(
GT.tr("Bad value for type timestamp/date/time: {1}", s),
PSQLState.BAD_DATETIME_FORMAT, nfe);
}
}
public LocalDateTime toLocalDateTime(String s) throws SQLException {
if (s == null) {
return null;
}
int slen = s.length();
if (slen == 8 && s.equals("infinity")) {
return LocalDateTime.MAX;
}
if (slen == 9 && s.equals("-infinity")) {
return LocalDateTime.MIN;
}
ParsedTimestamp ts = parseBackendTimestamp(s);
LocalDateTime result = LocalDateTime.of(ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, ts.nanos);
if (ts.era == GregorianCalendar.BC) {
return result.with(ChronoField.ERA, IsoEra.BCE.getValue());
} else {
return result;
}
}
public synchronized Time toTime(Calendar cal, String s) throws SQLException {
if (s == null) {
return null;
}
ParsedTimestamp ts = parseBackendTimestamp(s);
Calendar useCal = ts.tz != null ? ts.tz : setupCalendar(cal);
if (ts.tz == null) {
useCal.set(Calendar.ERA, ts.era);
useCal.set(Calendar.YEAR, ts.year);
useCal.set(Calendar.MONTH, ts.month - 1);
useCal.set(Calendar.DAY_OF_MONTH, ts.day);
} else {
useCal.set(Calendar.ERA, GregorianCalendar.AD);
useCal.set(Calendar.YEAR, 1970);
useCal.set(Calendar.MONTH, Calendar.JANUARY);
useCal.set(Calendar.DAY_OF_MONTH, 1);
}
useCal.set(Calendar.HOUR_OF_DAY, ts.hour);
useCal.set(Calendar.MINUTE, ts.minute);
useCal.set(Calendar.SECOND, ts.second);
useCal.set(Calendar.MILLISECOND, 0);
long timeMillis = useCal.getTimeInMillis() + ts.nanos / 1000000;
if (ts.tz != null || (ts.year == 1970 && ts.era == GregorianCalendar.AD)) {
return new Time(timeMillis);
}
return convertToTime(timeMillis, useCal.getTimeZone());
}
public synchronized Date toDate(Calendar cal, String s) throws SQLException {
Timestamp timestamp = toTimestamp(cal, s);
if (timestamp == null) {
return null;
}
return convertToDate(timestamp.getTime(), cal == null ? null : cal.getTimeZone());
}
private Calendar setupCalendar(Calendar cal) {
TimeZone timeZone = cal == null ? null : cal.getTimeZone();
return getSharedCalendar(timeZone);
}
public Calendar getSharedCalendar(TimeZone timeZone) {
if (timeZone == null) {
timeZone = getDefaultTz();
}
Calendar tmp = calendarWithUserTz;
tmp.setTimeZone(timeZone);
return tmp;
}
private static boolean nanosExceed499(int nanos) {
return nanos % 1000 > 499;
}
public synchronized String toString(Calendar cal, Timestamp x) {
return toString(cal, x, true);
}
public synchronized String toString(Calendar cal, Timestamp x,
boolean withTimeZone) {
if (x.getTime() == PGStatement.DATE_POSITIVE_INFINITY) {
return "infinity";
} else if (x.getTime() == PGStatement.DATE_NEGATIVE_INFINITY) {
return "-infinity";
}
cal = setupCalendar(cal);
long timeMillis = x.getTime();
int nanos = x.getNanos();
if (nanos >= MAX_NANOS_BEFORE_WRAP_ON_ROUND) {
nanos = 0;
timeMillis++;
} else if (nanosExceed499(nanos)) {
nanos += 1000 - nanos % 1000;
}
cal.setTimeInMillis(timeMillis);
sbuf.setLength(0);
appendDate(sbuf, cal);
sbuf.append(' ');
appendTime(sbuf, cal, nanos);
if (withTimeZone) {
appendTimeZone(sbuf, cal);
}
appendEra(sbuf, cal);
return sbuf.toString();
}
public synchronized String toString(Calendar cal, Date x) {
return toString(cal, x, true);
}
public synchronized String toString(Calendar cal, Date x,
boolean withTimeZone) {
if (x.getTime() == PGStatement.DATE_POSITIVE_INFINITY) {
return "infinity";
} else if (x.getTime() == PGStatement.DATE_NEGATIVE_INFINITY) {
return "-infinity";
}
cal = setupCalendar(cal);
cal.setTime(x);
sbuf.setLength(0);
appendDate(sbuf, cal);
appendEra(sbuf, cal);
if (withTimeZone) {
sbuf.append(' ');
appendTimeZone(sbuf, cal);
}
return sbuf.toString();
}
public synchronized String toString(Calendar cal, Time x) {
return toString(cal, x, true);
}
public synchronized String toString(Calendar cal, Time x,
boolean withTimeZone) {
cal = setupCalendar(cal);
cal.setTime(x);
sbuf.setLength(0);
appendTime(sbuf, cal, cal.get(Calendar.MILLISECOND) * 1000000);
if (withTimeZone) {
appendTimeZone(sbuf, cal);
}
return sbuf.toString();
}
private static void appendDate(StringBuilder sb, Calendar cal) {
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1;
int day = cal.get(Calendar.DAY_OF_MONTH);
appendDate(sb, year, month, day);
}
private static void appendDate(StringBuilder sb, int year, int month, int day) {
int prevLength = sb.length();
sb.append(year);
int leadingZerosForYear = 4 - (sb.length() - prevLength);
if (leadingZerosForYear > 0) {
sb.insert(prevLength, ZEROS, 0, leadingZerosForYear);
}
sb.append('-');
sb.append(NUMBERS[month]);
sb.append('-');
sb.append(NUMBERS[day]);
}
private static void appendTime(StringBuilder sb, Calendar cal, int nanos) {
int hours = cal.get(Calendar.HOUR_OF_DAY);
int minutes = cal.get(Calendar.MINUTE);
int seconds = cal.get(Calendar.SECOND);
appendTime(sb, hours, minutes, seconds, nanos);
}
private static void appendTime(StringBuilder sb, int hours, int minutes, int seconds, int nanos) {
sb.append(NUMBERS[hours]);
sb.append(':');
sb.append(NUMBERS[minutes]);
sb.append(':');
sb.append(NUMBERS[seconds]);
if (nanos < 1000) {
return;
}
sb.append('.');
int len = sb.length();
sb.append(nanos / 1000);
int needZeros = 6 - (sb.length() - len);
if (needZeros > 0) {
sb.insert(len, ZEROS, 0, needZeros);
}
int end = sb.length() - 1;
while (sb.charAt(end) == '0') {
sb.deleteCharAt(end);
end--;
}
}
private void appendTimeZone(StringBuilder sb, java.util.Calendar cal) {
int offset = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / 1000;
appendTimeZone(sb, offset);
}
private void appendTimeZone(StringBuilder sb, int offset) {
int absoff = Math.abs(offset);
int hours = absoff / 60 / 60;
int mins = (absoff - hours * 60 * 60) / 60;
int secs = absoff - hours * 60 * 60 - mins * 60;
sb.append((offset >= 0) ? "+" : "-");
sb.append(NUMBERS[hours]);
if (mins == 0 && secs == 0) {
return;
}
sb.append(':');
sb.append(NUMBERS[mins]);
if (secs != 0) {
sb.append(':');
sb.append(NUMBERS[secs]);
}
}
private static void appendEra(StringBuilder sb, Calendar cal) {
if (cal.get(Calendar.ERA) == GregorianCalendar.BC) {
sb.append(" BC");
}
}
public synchronized String toString(LocalDate localDate) {
if (LocalDate.MAX.equals(localDate)) {
return "infinity";
} else if (LocalDate.MIN.equals(localDate)) {
return "-infinity";
}
sbuf.setLength(0);
appendDate(sbuf, localDate);
appendEra(sbuf, localDate);
return sbuf.toString();
}
public synchronized String toString(LocalTime localTime) {
sbuf.setLength(0);
if (localTime.isAfter(MAX_TIME)) {
return "24:00:00";
}
int nano = localTime.getNano();
if (nanosExceed499(nano)) {
localTime = localTime.plus(ONE_MICROSECOND);
}
appendTime(sbuf, localTime);
return sbuf.toString();
}
public synchronized String toString(OffsetDateTime offsetDateTime) {
if (offsetDateTime.isAfter(MAX_OFFSET_DATETIME)) {
return "infinity";
} else if (OffsetDateTime.MIN.equals(offsetDateTime)) {
return "-infinity";
}
sbuf.setLength(0);
int nano = offsetDateTime.getNano();
if (nanosExceed499(nano)) {
offsetDateTime = offsetDateTime.plus(ONE_MICROSECOND);
}
LocalDateTime localDateTime = offsetDateTime.toLocalDateTime();
LocalDate localDate = localDateTime.toLocalDate();
appendDate(sbuf, localDate);
sbuf.append(' ');
appendTime(sbuf, localDateTime.toLocalTime());
appendTimeZone(sbuf, offsetDateTime.getOffset());
appendEra(sbuf, localDate);
return sbuf.toString();
}
public synchronized String toString(LocalDateTime localDateTime) {
if (localDateTime.isAfter(MAX_LOCAL_DATETIME)) {
return "infinity";
} else if (LocalDateTime.MIN.equals(localDateTime)) {
return "-infinity";
}
ZonedDateTime zonedDateTime = localDateTime.atZone(getDefaultTz().toZoneId());
return toString(zonedDateTime.toOffsetDateTime());
}
private static void appendDate(StringBuilder sb, LocalDate localDate) {
int year = localDate.get(ChronoField.YEAR_OF_ERA);
int month = localDate.getMonthValue();
int day = localDate.getDayOfMonth();
appendDate(sb, year, month, day);
}
private static void appendTime(StringBuilder sb, LocalTime localTime) {
int hours = localTime.getHour();
int minutes = localTime.getMinute();
int seconds = localTime.getSecond();
int nanos = localTime.getNano();
appendTime(sb, hours, minutes, seconds, nanos);
}
private void appendTimeZone(StringBuilder sb, ZoneOffset offset) {
int offsetSeconds = offset.getTotalSeconds();
appendTimeZone(sb, offsetSeconds);
}
private static void appendEra(StringBuilder sb, LocalDate localDate) {
if (localDate.get(ChronoField.ERA) == IsoEra.BCE.getValue()) {
sb.append(" BC");
}
}
private static int skipWhitespace(char[] s, int start) {
int slen = s.length;
for (int i = start; i < slen; i++) {
if (!Character.isSpace(s[i])) {
return i;
}
}
return slen;
}
private static int firstNonDigit(char[] s, int start) {
int slen = s.length;
for (int i = start; i < slen; i++) {
if (!Character.isDigit(s[i])) {
return i;
}
}
return slen;
}
private static int number(char[] s, int start, int end) {
if (start >= end) {
throw new NumberFormatException();
}
int n = 0;
for (int i = start; i < end; i++) {
n = 10 * n + (s[i] - '0');
}
return n;
}
private static char charAt(char[] s, int pos) {
if (pos >= 0 && pos < s.length) {
return s[pos];
}
return '\0';
}
public Date toDateBin(TimeZone tz, byte[] bytes) throws PSQLException {
if (bytes.length != 4) {
throw new PSQLException(GT.tr("Unsupported binary encoding of {0}.", "date"),
PSQLState.BAD_DATETIME_FORMAT);
}
int days = ByteConverter.int4(bytes, 0);
if (tz == null) {
tz = getDefaultTz();
}
long secs = toJavaSecs(days * 86400L);
long millis = secs * 1000L;
if (millis <= PGStatement.DATE_NEGATIVE_SMALLER_INFINITY) {
millis = PGStatement.DATE_NEGATIVE_INFINITY;
} else if (millis >= PGStatement.DATE_POSITIVE_SMALLER_INFINITY) {
millis = PGStatement.DATE_POSITIVE_INFINITY;
} else {
millis = guessTimestamp(millis, tz);
}
return new Date(millis);
}
private TimeZone getDefaultTz() {
if (DEFAULT_TIME_ZONE_FIELD != null) {
try {
TimeZone defaultTimeZone = (TimeZone) DEFAULT_TIME_ZONE_FIELD.get(null);
if (defaultTimeZone == prevDefaultZoneFieldValue) {
return defaultTimeZoneCache;
}
prevDefaultZoneFieldValue = defaultTimeZone;
} catch (Exception e) {
}
}
TimeZone tz = TimeZone.getDefault();
defaultTimeZoneCache = tz;
return tz;
}
public boolean hasFastDefaultTimeZone() {
return DEFAULT_TIME_ZONE_FIELD != null;
}
public Time toTimeBin(TimeZone tz, byte[] bytes) throws PSQLException {
if ((bytes.length != 8 && bytes.length != 12)) {
throw new PSQLException(GT.tr("Unsupported binary encoding of {0}.", "time"),
PSQLState.BAD_DATETIME_FORMAT);
}
long millis;
int timeOffset;
if (usesDouble) {
double time = ByteConverter.float8(bytes, 0);
millis = (long) (time * 1000);
} else {
long time = ByteConverter.int8(bytes, 0);
millis = time / 1000;
}
if (bytes.length == 12) {
timeOffset = ByteConverter.int4(bytes, 8);
timeOffset *= -1000;
millis -= timeOffset;
return new Time(millis);
}
if (tz == null) {
tz = getDefaultTz();
}
millis = guessTimestamp(millis, tz);
return convertToTime(millis, tz);
}
public LocalTime toLocalTimeBin(byte[] bytes) throws PSQLException {
if (bytes.length != 8) {
throw new PSQLException(GT.tr("Unsupported binary encoding of {0}.", "time"),
PSQLState.BAD_DATETIME_FORMAT);
}
long micros;
if (usesDouble) {
double seconds = ByteConverter.float8(bytes, 0);
micros = (long) (seconds * 1000000d);
} else {
micros = ByteConverter.int8(bytes, 0);
}
return LocalTime.ofNanoOfDay(micros * 1000);
}
public Timestamp toTimestampBin(TimeZone tz, byte[] bytes, boolean timestamptz)
throws PSQLException {
ParsedBinaryTimestamp parsedTimestamp = this.toParsedTimestampBin(tz, bytes, timestamptz);
if (parsedTimestamp.infinity == Infinity.POSITIVE) {
return new Timestamp(PGStatement.DATE_POSITIVE_INFINITY);
} else if (parsedTimestamp.infinity == Infinity.NEGATIVE) {
return new Timestamp(PGStatement.DATE_NEGATIVE_INFINITY);
}
Timestamp ts = new Timestamp(parsedTimestamp.millis);
ts.setNanos(parsedTimestamp.nanos);
return ts;
}
private ParsedBinaryTimestamp toParsedTimestampBin(TimeZone tz, byte[] bytes, boolean timestamptz)
throws PSQLException {
if (bytes.length != 8) {
throw new PSQLException(GT.tr("Unsupported binary encoding of {0}.", "timestamp"),
PSQLState.BAD_DATETIME_FORMAT);
}
long secs;
int nanos;
if (usesDouble) {
double time = ByteConverter.float8(bytes, 0);
if (time == Double.POSITIVE_INFINITY) {
ParsedBinaryTimestamp ts = new ParsedBinaryTimestamp();
ts.infinity = Infinity.POSITIVE;
return ts;
} else if (time == Double.NEGATIVE_INFINITY) {
ParsedBinaryTimestamp ts = new ParsedBinaryTimestamp();
ts.infinity = Infinity.NEGATIVE;
return ts;
}
secs = (long) time;
nanos = (int) ((time - secs) * 1000000);
} else {
long time = ByteConverter.int8(bytes, 0);
if (time == Long.MAX_VALUE) {
ParsedBinaryTimestamp ts = new ParsedBinaryTimestamp();
ts.infinity = Infinity.POSITIVE;
return ts;
} else if (time == Long.MIN_VALUE) {
ParsedBinaryTimestamp ts = new ParsedBinaryTimestamp();
ts.infinity = Infinity.NEGATIVE;
return ts;
}
secs = time / 1000000;
nanos = (int) (time - secs * 1000000);
}
if (nanos < 0) {
secs--;
nanos += 1000000;
}
nanos *= 1000;
secs = toJavaSecs(secs);
long millis = secs * 1000L;
if (!timestamptz) {
millis = guessTimestamp(millis, tz);
}
ParsedBinaryTimestamp ts = new ParsedBinaryTimestamp();
ts.millis = millis;
ts.nanos = nanos;
return ts;
}
public LocalDateTime toLocalDateTimeBin(TimeZone tz, byte[] bytes) throws PSQLException {
ParsedBinaryTimestamp parsedTimestamp = this.toParsedTimestampBin(tz, bytes, true);
if (parsedTimestamp.infinity == Infinity.POSITIVE) {
return LocalDateTime.MAX;
} else if (parsedTimestamp.infinity == Infinity.NEGATIVE) {
return LocalDateTime.MIN;
}
return LocalDateTime.ofEpochSecond(parsedTimestamp.millis / 1000L, parsedTimestamp.nanos, ZoneOffset.UTC);
}
private long guessTimestamp(long millis, TimeZone tz) {
if (tz == null) {
tz = getDefaultTz();
}
if (isSimpleTimeZone(tz.getID())) {
return millis - tz.getRawOffset();
}
Calendar cal = calendarWithUserTz;
cal.setTimeZone(utcTz);
cal.setTimeInMillis(millis);
int era = cal.get(Calendar.ERA);
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH);
int day = cal.get(Calendar.DAY_OF_MONTH);
int hour = cal.get(Calendar.HOUR_OF_DAY);
int min = cal.get(Calendar.MINUTE);
int sec = cal.get(Calendar.SECOND);
int ms = cal.get(Calendar.MILLISECOND);
cal.setTimeZone(tz);
cal.set(Calendar.ERA, era);
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, month);
cal.set(Calendar.DAY_OF_MONTH, day);
cal.set(Calendar.HOUR_OF_DAY, hour);
cal.set(Calendar.MINUTE, min);
cal.set(Calendar.SECOND, sec);
cal.set(Calendar.MILLISECOND, ms);
return cal.getTimeInMillis();
}
private static boolean isSimpleTimeZone(String id) {
return id.startsWith("GMT") || id.startsWith("UTC");
}
public Date convertToDate(long millis, TimeZone tz) {
if (millis <= PGStatement.DATE_NEGATIVE_INFINITY
|| millis >= PGStatement.DATE_POSITIVE_INFINITY) {
return new Date(millis);
}
if (tz == null) {
tz = getDefaultTz();
}
if (isSimpleTimeZone(tz.getID())) {
int offset = tz.getRawOffset();
millis += offset;
millis = floorDiv(millis, ONEDAY) * ONEDAY;
millis -= offset;
return new Date(millis);
}
Calendar cal = calendarWithUserTz;
cal.setTimeZone(tz);
cal.setTimeInMillis(millis);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
return new Date(cal.getTimeInMillis());
}
public Time convertToTime(long millis, TimeZone tz) {
if (tz == null) {
tz = getDefaultTz();
}
if (isSimpleTimeZone(tz.getID())) {
int offset = tz.getRawOffset();
millis += offset;
millis = floorMod(millis, ONEDAY);
millis -= offset;
return new Time(millis);
}
Calendar cal = calendarWithUserTz;
cal.setTimeZone(tz);
cal.setTimeInMillis(millis);
cal.set(Calendar.ERA, GregorianCalendar.AD);
cal.set(Calendar.YEAR, 1970);
cal.set(Calendar.MONTH, 0);
cal.set(Calendar.DAY_OF_MONTH, 1);
return new Time(cal.getTimeInMillis());
}
public String timeToString(java.util.Date time, boolean withTimeZone) {
Calendar cal = null;
if (withTimeZone) {
cal = calendarWithUserTz;
cal.setTimeZone(timeZoneProvider.get());
}
if (time instanceof Timestamp) {
return toString(cal, (Timestamp) time, withTimeZone);
}
if (time instanceof Time) {
return toString(cal, (Time) time, withTimeZone);
}
return toString(cal, (Date) time, withTimeZone);
}
private static long toJavaSecs(long secs) {
secs += 946684800L;
if (secs < -12219292800L) {
secs += 86400 * 10;
if (secs < -14825808000L) {
int extraLeaps = (int) ((secs + 14825808000L) / 3155760000L);
extraLeaps--;
extraLeaps -= extraLeaps / 4;
secs += extraLeaps * 86400L;
}
}
return secs;
}
private static long toPgSecs(long secs) {
secs -= 946684800L;
if (secs < -13165977600L) {
secs -= 86400 * 10;
if (secs < -15773356800L) {
int years = (int) ((secs + 15773356800L) / -3155823050L);
years++;
years -= years / 4;
secs += years * 86400L;
}
}
return secs;
}
public void toBinDate(TimeZone tz, byte[] bytes, Date value) throws PSQLException {
long millis = value.getTime();
if (tz == null) {
tz = getDefaultTz();
}
millis += tz.getOffset(millis);
long secs = toPgSecs(millis / 1000);
ByteConverter.int4(bytes, 0, (int) (secs / 86400));
}
public static TimeZone parseBackendTimeZone(String timeZone) {
if (timeZone.startsWith("GMT")) {
TimeZone tz = GMT_ZONES.get(timeZone);
if (tz != null) {
return tz;
}
}
return TimeZone.getTimeZone(timeZone);
}
private static long floorDiv(long x, long y) {
long r = x / y;
if ((x ^ y) < 0 && (r * y != x)) {
r--;
}
return r;
}
private static long floorMod(long x, long y) {
return x - floorDiv(x, y) * y;
}
}