Skip to content

Commit

Permalink
improved jsonb readDate support, for issue #2408
Browse files Browse the repository at this point in the history
  • Loading branch information
wenshao committed Apr 9, 2024
1 parent e7de4d7 commit deedf37
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 33 deletions.
4 changes: 4 additions & 0 deletions core/src/main/java/com/alibaba/fastjson2/JSONReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -1447,6 +1447,10 @@ public LocalTime readLocalTime() {

protected abstract int getStringLength();

public boolean isDate() {
return false;
}

public Instant readInstant() {
if (nextIfNull()) {
return null;
Expand Down
147 changes: 114 additions & 33 deletions core/src/main/java/com/alibaba/fastjson2/JSONReaderJSONB.java
Original file line number Diff line number Diff line change
Expand Up @@ -731,39 +731,7 @@ public Object readAny() {
return LocalDateTime.of(year, month, dayOfMonth, hour, minute, second, nano);
}
case BC_TIMESTAMP_WITH_TIMEZONE: {
int year = (bytes[offset++] << 8) + (bytes[offset++] & 0xFF);
byte month = bytes[offset++];
byte dayOfMonth = bytes[offset++];
byte hour = bytes[offset++];
byte minute = bytes[offset++];
byte second = bytes[offset++];
int nano = readInt32Value();
// SHANGHAI_ZONE_ID_NAME_BYTES
ZoneId zoneId;
{
boolean shanghai;
byte[] shanghaiZoneIdNameBytes = SHANGHAI_ZONE_ID_NAME_BYTES;
if (offset + shanghaiZoneIdNameBytes.length < bytes.length) {
shanghai = true;
for (int i = 0; i < shanghaiZoneIdNameBytes.length; ++i) {
if (bytes[offset + i] != shanghaiZoneIdNameBytes[i]) {
shanghai = false;
break;
}
}
} else {
shanghai = false;
}
if (shanghai) {
offset += shanghaiZoneIdNameBytes.length;
zoneId = SHANGHAI_ZONE_ID;
} else {
String zoneIdStr = readString();
zoneId = DateUtils.getZoneId(zoneIdStr, SHANGHAI_ZONE_ID);
}
}
LocalDateTime ldt = LocalDateTime.of(year, month, dayOfMonth, hour, minute, second, nano);
return ZonedDateTime.of(ldt, zoneId);
return readTimestampWithTimeZone();
}
case BC_TIMESTAMP: {
long epochSeconds = readInt64Value();
Expand Down Expand Up @@ -1059,6 +1027,43 @@ public Object readAny() {
}
}

private ZonedDateTime readTimestampWithTimeZone() {
byte[] bytes = this.bytes;
int year = (bytes[offset++] << 8) + (bytes[offset++] & 0xFF);
byte month = bytes[offset++];
byte dayOfMonth = bytes[offset++];
byte hour = bytes[offset++];
byte minute = bytes[offset++];
byte second = bytes[offset++];
int nano = readInt32Value();
// SHANGHAI_ZONE_ID_NAME_BYTES
ZoneId zoneId;
{
boolean shanghai;
byte[] shanghaiZoneIdNameBytes = SHANGHAI_ZONE_ID_NAME_BYTES;
if (offset + shanghaiZoneIdNameBytes.length < bytes.length) {
shanghai = true;
for (int i = 0; i < shanghaiZoneIdNameBytes.length; ++i) {
if (bytes[offset + i] != shanghaiZoneIdNameBytes[i]) {
shanghai = false;
break;
}
}
} else {
shanghai = false;
}
if (shanghai) {
offset += shanghaiZoneIdNameBytes.length;
zoneId = SHANGHAI_ZONE_ID;
} else {
String zoneIdStr = readString();
zoneId = DateUtils.getZoneId(zoneIdStr, SHANGHAI_ZONE_ID);
}
}
LocalDateTime ldt = LocalDateTime.of(year, month, dayOfMonth, hour, minute, second, nano);
return ZonedDateTime.of(ldt, zoneId);
}

@Override
public byte getType() {
return bytes[offset];
Expand Down Expand Up @@ -5365,6 +5370,82 @@ protected int getStringLength() {
throw new UnsupportedOperationException();
}

public boolean isDate() {
byte type = bytes[offset];
return type >= BC_LOCAL_TIME && type <= BC_TIMESTAMP;
}

public Date readDate() {
ZonedDateTime zdt = null;
int offset = this.offset;
byte[] bytes = this.bytes;
byte type = bytes[offset];
switch (type) {
case BC_LOCAL_TIME: {
LocalTime localTime = readLocalTime();
LocalDateTime ldt = LocalDateTime.of(LocalDate.of(1970, 1, 1), localTime);
zdt = ZonedDateTime.ofLocal(ldt, context.getZoneId(), null);
break;
}
case BC_LOCAL_DATETIME:
LocalDateTime ldt = readLocalDateTime();
zdt = ZonedDateTime.ofLocal(ldt, context.getZoneId(), null);
break;
case BC_LOCAL_DATE:
LocalDate localDate = readLocalDate();
zdt = ZonedDateTime.ofLocal(
LocalDateTime.of(localDate, LocalTime.MIN),
context.getZoneId(),
null);
break;
case BC_TIMESTAMP_MILLIS: {
long millis = UNSAFE.getLong(bytes, ARRAY_BYTE_BASE_OFFSET + offset + 1);
this.offset += 9;
return new Date(BIG_ENDIAN ? millis : Long.reverseBytes(millis));
}
case BC_TIMESTAMP_MINUTES: {
long minutes = getInt(bytes, offset + 1);
this.offset += 5;
return new Date(minutes * 60L * 1000L);
}
case BC_TIMESTAMP_SECONDS: {
long seconds = getInt(bytes, offset + 1);
this.offset += 5;
return new Date(seconds * 1000);
}
case BC_TIMESTAMP_WITH_TIMEZONE: {
this.offset = offset + 1;
zdt = readTimestampWithTimeZone();
break;
}
case BC_TIMESTAMP: {
this.offset = offset + 1;
long epochSeconds = readInt64Value();
int nano = readInt32Value();
return Date.from(
Instant.ofEpochSecond(epochSeconds, nano));
}
default:
break;
}

if (zdt != null) {
long seconds = zdt.toEpochSecond();
int nanos = zdt.toLocalTime().getNano();
long millis;
if (seconds < 0 && nanos > 0) {
millis = (seconds + 1) * 1000;
long adjustment = nanos / 1000_000 - 1000;
millis += adjustment;
} else {
millis = seconds * 1000L;
millis += nanos / 1000_000;
}
return new Date(millis);
}
return super.readDate();
}

@Override
public LocalDate readLocalDate8() {
LocalDate ldt;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ private Object readDate(JSONReader jsonReader) {
millis += nanos / 1000_000;
}
} else {
if (jsonReader.isDate()) {
return jsonReader.readDate();
}

if (jsonReader.isTypeRedirect() && jsonReader.nextIfMatchIdent('"', 'v', 'a', 'l', '"')) {
jsonReader.nextIfMatch(':');
millis = jsonReader.readInt64Value();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.alibaba.fastjson2.issues_2400;

import com.alibaba.fastjson2.JSONB;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.util.DateUtils;
import org.junit.jupiter.api.Test;

import java.sql.Timestamp;
import java.time.*;
import java.util.Date;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class Issue2408 {
static final long millis = 1712557951977L;
static final long seconds = millis / 1000;
static final Instant instant = new Date(millis).toInstant();

@Test
public void test() {
Timestamp timestamp = new Timestamp(millis);
byte[] bytes = JSONB.toBytes(timestamp, JSONWriter.Feature.WriteClassName);
Date date = JSONB.parseObject(bytes, Date.class);
assertEquals(millis, date.getTime());
}

@Test
public void testInstant() {
byte[] bytes = JSONB.toBytes(instant);
Date date = JSONB.parseObject(bytes, Date.class);
assertEquals(millis, date.getTime());
}

@Test
public void testLocalDateTime() {
LocalDateTime ldt = LocalDateTime.ofInstant(instant, DateUtils.DEFAULT_ZONE_ID);
byte[] bytes = JSONB.toBytes(ldt);
Date date = JSONB.parseObject(bytes, Date.class);
assertEquals(millis, date.getTime());
}

@Test
public void testLocalTime() {
LocalDateTime ldt = LocalDateTime.ofInstant(instant, DateUtils.DEFAULT_ZONE_ID);
LocalTime localTime = ldt.toLocalTime();
byte[] bytes = JSONB.toBytes(localTime);
Date date = JSONB.parseObject(bytes, Date.class);
assertEquals(23551977, date.getTime());
}

@Test
public void testLocalDate() {
LocalDateTime ldt = LocalDateTime.ofInstant(instant, DateUtils.DEFAULT_ZONE_ID);
LocalDate localDate = ldt.toLocalDate();
byte[] bytes = JSONB.toBytes(localDate);
Date date = JSONB.parseObject(bytes, Date.class);
assertEquals(1712505600000L, date.getTime());
}

@Test
public void testZonedDateTime() {
ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, DateUtils.DEFAULT_ZONE_ID);
byte[] bytes = JSONB.toBytes(zdt);
Date date = JSONB.parseObject(bytes, Date.class);
assertEquals(millis, date.getTime());
}

@Test
public void testSeconds() {
JSONWriter jsonWriter = JSONWriter.ofJSONB();
jsonWriter.writeMillis(seconds * 1000);
byte[] bytes = jsonWriter.getBytes();
Date date = JSONReader.ofJSONB(bytes)
.readDate();
assertEquals(seconds * 1000, date.getTime());
}

@Test
public void testMillis() {
JSONWriter jsonWriter = JSONWriter.ofJSONB();
jsonWriter.writeMillis(millis);
byte[] bytes = jsonWriter.getBytes();
Date result = JSONReader.ofJSONB(bytes)
.readDate();
assertEquals(millis, result.getTime());
}

@Test
public void testMinutes() {
long millis = 2712505800000L;
JSONWriter jsonWriter = JSONWriter.ofJSONB();
jsonWriter.writeMillis(millis);
byte[] bytes = jsonWriter.getBytes();
Date date = JSONReader.ofJSONB(bytes).readDate();
assertEquals(millis, date.getTime());
}
}

0 comments on commit deedf37

Please sign in to comment.