Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -220,25 +220,51 @@ public static Time parseTime(String timeValue) {
}

/**
* Parses the timestsamp string in the UTC time zone.
* Parses the timestamp string in the UTC time zone.
* @param timestampValue timestamp string in UTC
* @return Timestamp parsed in UTC
*/
public static Timestamp parseTimestamp(String timestampValue) {
Timestamp timestamp = new Timestamp(parseDateTime(timestampValue));
return parseTimestamp(timestampValue, null);
}

/**
* Parses the timestamp string with the given parser.
* @param timestampValue timestamp string
* @param parser Parser with user defined time zone.
* @return Timestamp parsed by the given parser, or parsed in UTC if parser is null.
*/
public static Timestamp parseTimestamp(String timestampValue, DateTimeParser parser) {
Timestamp timestamp;
if (parser == null) {
timestamp = new Timestamp(parseDateTime(timestampValue));
} else {
timestamp = new Timestamp(parser.parseDateTime(timestampValue));
}
int nanos = getNanosFromTimestamp(timestampValue);
if (nanos > -1) {
// First clear existing nanos (default is 0), then set the new nanos value
// to avoid accumulating nanos from previous operations
timestamp.setNanos(0);
timestamp.setNanos(nanos);
}
return timestamp;
}

private static int getNanosFromTimestamp(String timestampValue) {
int nanos = -1;
int period = timestampValue.indexOf('.');
if (period > 0) {
String nanosStr = timestampValue.substring(period + 1);
if (nanosStr.length() > 9) throw new IllegalDataException("nanos > 999999999 or < 0");
if (nanosStr.length() > 3) {
int nanos = Integer.parseInt(nanosStr);
nanos = Integer.parseInt(nanosStr);
for (int i = 0; i < 9 - nanosStr.length(); i++) {
nanos *= 10;
}
timestamp.setNanos(nanos);
}
}
return timestamp;
return nanos;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ public Object apply(@Nullable String input) {
return null;
}
if (dataType == PTimestamp.INSTANCE) {
return DateUtil.parseTimestamp(input);
return DateUtil.parseTimestamp(input, dateTimeParser);
}
if (dateTimeParser != null) {
long epochTime = dateTimeParser.parseDateTime(input);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ public Object apply(@Nullable Object input) {
return null;
}
if (dataType == PTimestamp.INSTANCE) {
return DateUtil.parseTimestamp(input.toString());
return DateUtil.parseTimestamp(input.toString(), dateTimeParser);
}
if (dateTimeParser != null && input instanceof String) {
final String s = (String) input;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,38 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;

import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.io.IOException;
import java.text.ParseException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.TimeZone;

import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.util.ColumnInfo;
import org.apache.phoenix.util.DateUtil;
import org.apache.phoenix.schema.IllegalDataException;
import org.apache.phoenix.schema.types.PDate;
import org.apache.phoenix.schema.types.PInteger;
import org.apache.phoenix.schema.types.PIntegerArray;
import org.apache.phoenix.schema.types.PTime;
import org.apache.phoenix.schema.types.PTimestamp;
import org.junit.Test;
import org.mockito.ArgumentCaptor;

import org.apache.phoenix.thirdparty.com.google.common.base.Joiner;
import org.apache.phoenix.thirdparty.com.google.common.collect.Iterables;

/**
* Test class for {@link DateUtil}
Expand Down Expand Up @@ -352,4 +369,49 @@ public void testTZCorrection() {
assertEquals(DateUtil.applyInputDisplacement(startOfWinterLocal, tz), startOfWinterDisplaced);
assertEquals(DateUtil.applyOutputDisplacement(startOfWinterDisplaced, tz), startOfWinterLocal);
}

/**
* parseTimestamp should use the configured timezone from parser instead of hardcoded UTC.
* Test logic:
* 1. Create a DateTimeParser with Asia/Shanghai timezone
* 2. Parse timestamp string "2020-01-01 00:00:00.000" using the parser
* 3. Verify parsed epoch millis is 8 hours less than UTC parsed result
*/
@Test
public void testParseTimestampWithCustomTimeZone() throws ParseException {
String tsStr = "2020-01-01 00:00:00.000";
java.util.TimeZone shanghaiZone = java.util.TimeZone.getTimeZone("Asia/Shanghai");

java.sql.Timestamp parsedTs = DateUtil.parseTimestamp(tsStr,
DateUtil.getDateTimeParser("yyyy-MM-dd HH:mm:ss.SSS", PTimestamp.INSTANCE, shanghaiZone.getID()));

// DateUtil.parseTimestamp() parses using UTC by default when no parser is provided
java.sql.Timestamp utcTs = DateUtil.parseTimestamp(tsStr);

// Asia/Shanghai is UTC+8, so the parsed epoch time should be 8 hours less than UTC result
long expectedMillis = utcTs.getTime() - (8L * 60 * 60 * 1000);

assertEquals("Timestamp should be parsed using configured timezone Asia/Shanghai", expectedMillis, parsedTs.getTime());
}

/**
* Verifies that nanosecond precision is correctly preserved under a custom timezone
*/
@Test
public void testParseTimestampWithCustomTimeZoneAndNanos() throws ParseException {
String tsStr = "2020-01-01 00:00:00.123"; // Only 3 nanoseconds
java.util.TimeZone shanghaiZone = java.util.TimeZone.getTimeZone("Asia/Shanghai");

java.sql.Timestamp parsedTs = DateUtil.parseTimestamp(tsStr,
DateUtil.getDateTimeParser("yyyy-MM-dd HH:mm:ss.SSS", PTimestamp.INSTANCE, shanghaiZone.getID()));

// DateUtil.parseTimestamp() parses using UTC by default when no parser is provided
java.sql.Timestamp utcTs = DateUtil.parseTimestamp(tsStr);

// Asia/Shanghai is UTC+8, so the parsed epoch time should be 8 hours less than UTC result
long expectedMillis = utcTs.getTime() - (8L * 60 * 60 * 1000);

assertEquals("Epoch millis should reflect Asia/Shanghai timezone", expectedMillis, parsedTs.getTime());
assertEquals("Nanos should be preserved correctly", 123000000, parsedTs.getNanos());
}
}