Skip to content
Merged
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 @@ -16,6 +16,7 @@
import io.arex.foundation.serializer.gson.adapter.OffsetDateTimeAdapter;
import io.arex.foundation.serializer.gson.adapter.TimeZoneAdapter;
import io.arex.foundation.serializer.gson.adapter.XMLGregorianCalendarAdapter;
import io.arex.foundation.serializer.gson.adapter.ZonedDateTimeAdapter;
import io.arex.inst.runtime.serializer.StringSerializable;

import javax.xml.datatype.XMLGregorianCalendar;
Expand All @@ -27,6 +28,7 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
Expand Down Expand Up @@ -54,6 +56,7 @@ public GsonRequestSerializer() {
.registerTypeAdapter(LocalTime.class, new LocalTimeAdapter.RequestSerializer())
.registerTypeAdapter(Class.class, new ClassAdapter.Serializer())
.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter.RequestSerializer())
.registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter.RequestSerializer())
.registerTypeHierarchyAdapter(TimeZone.class, new TimeZoneAdapter.Serializer())
.registerTypeAdapter(String.class, new StringAdapter.Serializer())
.registerTypeAdapterFactory(new CustomTypeAdapterFactory.RequestSerializerFactory())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.sql.Time;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import com.google.gson.*;

import javax.xml.datatype.XMLGregorianCalendar;
Expand Down Expand Up @@ -72,6 +73,8 @@ public GsonSerializer() {
.registerTypeAdapter(Class.class, new ClassAdapter.Deserializer())
.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter.Serializer())
.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter.Deserializer())
.registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter.Serializer())
.registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter.Deserializer())
.registerTypeHierarchyAdapter(TimeZone.class, new TimeZoneAdapter.Serializer())
.registerTypeAdapter(TimeZone.class, new TimeZoneAdapter.Deserializer())
.registerTypeAdapterFactory(new FastUtilAdapterFactory())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.arex.foundation.serializer.gson.adapter;

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import io.arex.agent.thirdparty.util.time.DateFormatUtils;
import io.arex.foundation.serializer.util.DateFormatParser;
import io.arex.foundation.serializer.util.DenoisingUtil;
import io.arex.foundation.serializer.util.TimePatternConstants;

import java.lang.reflect.Type;
import java.time.ZonedDateTime;

public class ZonedDateTimeAdapter {
private ZonedDateTimeAdapter() {
}

public static class RequestSerializer implements JsonSerializer<ZonedDateTime> {
@Override
public JsonElement serialize(ZonedDateTime src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(DenoisingUtil.zeroSecondTime(src));
}
}

public static class Serializer implements JsonSerializer<ZonedDateTime> {
@Override
public JsonElement serialize(ZonedDateTime src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(DateFormatUtils.format(src, TimePatternConstants.zonedDateTimeFormat));
}
}

public static class Deserializer implements JsonDeserializer<ZonedDateTime> {
@Override
public ZonedDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return ZonedDateTime.parse(json.getAsString(),
DateFormatParser.INSTANCE.getFormatter(TimePatternConstants.zonedDateTimeFormat));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import io.arex.foundation.serializer.jackson.adapter.LocalTimeAdapter;
import io.arex.foundation.serializer.jackson.adapter.OffsetDateTimeAdapter;
import io.arex.foundation.serializer.jackson.adapter.XMLGregorianCalendarAdapter;
import io.arex.foundation.serializer.jackson.adapter.ZonedDateTimeAdapter;
import io.arex.inst.runtime.serializer.StringSerializable;

import javax.xml.datatype.XMLGregorianCalendar;
Expand All @@ -26,6 +27,7 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
Expand Down Expand Up @@ -57,6 +59,7 @@ private void customTimeFormatSerializer(SimpleModule module) {
module.addSerializer(Date.class, dateRequestSerializer);
module.addSerializer(Instant.class, new InstantAdapter.RequestSerializer());
module.addSerializer(OffsetDateTime.class, new OffsetDateTimeAdapter.RequestSerializer());
module.addSerializer(ZonedDateTime.class, new ZonedDateTimeAdapter.RequestSerializer());
module.addSerializer(String.class, new StringAdapter.Serializer());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import io.arex.foundation.serializer.jackson.adapter.SqlTimeAdapter;
import io.arex.foundation.serializer.jackson.adapter.TimestampAdapter;
import io.arex.foundation.serializer.jackson.adapter.XMLGregorianCalendarAdapter;
import io.arex.foundation.serializer.jackson.adapter.ZonedDateTimeAdapter;
import io.arex.inst.runtime.log.LogManager;
import io.arex.inst.runtime.serializer.StringSerializable;

Expand All @@ -41,6 +42,7 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
Expand Down Expand Up @@ -151,6 +153,7 @@ private void customTimeFormatSerializer(SimpleModule module) {
module.addSerializer(Date.class, dateSerializer);
module.addSerializer(Instant.class, new InstantAdapter.Serializer());
module.addSerializer(OffsetDateTime.class, new OffsetDateTimeAdapter.Serializer());
module.addSerializer(ZonedDateTime.class, new ZonedDateTimeAdapter.Serializer());
}

private void customTimeFormatDeserializer(SimpleModule module) {
Expand All @@ -164,6 +167,7 @@ private void customTimeFormatDeserializer(SimpleModule module) {
module.addDeserializer(Date.class, new DateAdapter.Deserializer());
module.addDeserializer(Instant.class, new InstantAdapter.Deserializer());
module.addDeserializer(OffsetDateTime.class, new OffsetDateTimeAdapter.Deserializer());
module.addDeserializer(ZonedDateTime.class, new ZonedDateTimeAdapter.Deserializer());
module.addDeserializer(java.sql.Date.class, new SqlDateAdapter.Deserializer());
module.addDeserializer(Time.class, new SqlTimeAdapter.Deserializer());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.arex.foundation.serializer.jackson.adapter.SqlTimeAdapter;
import io.arex.foundation.serializer.jackson.adapter.TimestampAdapter;
import io.arex.foundation.serializer.jackson.adapter.XMLGregorianCalendarAdapter;
import io.arex.foundation.serializer.jackson.adapter.ZonedDateTimeAdapter;
import io.arex.inst.runtime.log.LogManager;
import io.arex.inst.runtime.serializer.StringSerializable;

Expand All @@ -35,6 +36,7 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
Expand Down Expand Up @@ -89,6 +91,7 @@ private void customTimeFormatSerializer(SimpleModule module) {
module.addSerializer(Date.class, dateSerializer);
module.addSerializer(Instant.class, new InstantAdapter.Serializer());
module.addSerializer(OffsetDateTime.class, new OffsetDateTimeAdapter.Serializer());
module.addSerializer(ZonedDateTime.class, new ZonedDateTimeAdapter.Serializer());
}

private void customTimeFormatDeserializer(SimpleModule module) {
Expand All @@ -102,6 +105,7 @@ private void customTimeFormatDeserializer(SimpleModule module) {
module.addDeserializer(Date.class, new DateAdapter.Deserializer());
module.addDeserializer(Instant.class, new InstantAdapter.Deserializer());
module.addDeserializer(OffsetDateTime.class, new OffsetDateTimeAdapter.Deserializer());
module.addDeserializer(ZonedDateTime.class, new ZonedDateTimeAdapter.Deserializer());
module.addDeserializer(java.sql.Date.class, new SqlDateAdapter.Deserializer());
module.addDeserializer(Time.class, new SqlTimeAdapter.Deserializer());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.arex.foundation.serializer.jackson.adapter;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import io.arex.foundation.serializer.util.DateFormatParser;
import io.arex.foundation.serializer.util.DenoisingUtil;
import io.arex.foundation.serializer.util.TimePatternConstants;

import java.io.IOException;
import java.time.ZonedDateTime;

public class ZonedDateTimeAdapter {
private ZonedDateTimeAdapter() {
}

public static class RequestSerializer extends JsonSerializer<ZonedDateTime> {
@Override
public void serialize(ZonedDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(DenoisingUtil.zeroSecondTime(value));
}
}

public static class Serializer extends JsonSerializer<ZonedDateTime> {
@Override
public void serialize(ZonedDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(value.format(DateFormatParser.INSTANCE.getFormatter(TimePatternConstants.zonedDateTimeFormat)));
}

@Override
public void serializeWithType(ZonedDateTime value, JsonGenerator gen, SerializerProvider serializers,
TypeSerializer typeSer) throws IOException {
WritableTypeId writableTypeId = typeSer.writeTypePrefix(gen, typeSer.typeId(value, JsonToken.VALUE_STRING));
serialize(value, gen, serializers);
typeSer.writeTypeSuffix(gen, writableTypeId);
}
}

public static class Deserializer extends JsonDeserializer<ZonedDateTime> {
@Override
public ZonedDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode node = p.getCodec().readTree(p);
return ZonedDateTime.parse(node.asText(),
DateFormatParser.INSTANCE.getFormatter(TimePatternConstants.zonedDateTimeFormat));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
Expand Down Expand Up @@ -100,6 +101,11 @@ public static String zeroSecondTime(OffsetDateTime offsetDateTime) {
return time + TimePatternConstants.ZERO_SECOND_TIME_REQUEST + timeZone;
}

public static String zeroSecondTime(ZonedDateTime zonedDateTime) {
// the zone id is placed at the end of the pattern, format the zeroed value instead of concat
return DateFormatUtils.format(zonedDateTime.withSecond(0).withNano(0), TimePatternConstants.zonedDateTimeFormat);
}

public static String zeroSecondTime(Instant instant) {
return DateTimeFormatter.ofPattern(TimePatternConstants.SIMPLE_DATE_FORMAT_MILLIS_REQUEST).
withZone(ZoneId.systemDefault()).format(instant) +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ private TimePatternConstants() {
*/
public static final String SIMPLE_DATE_FORMAT_WITH_TIMEZONE = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZ";
public static final String SIMPLE_DATE_FORMAT_WITH_TIMEZONE_DATETIME = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ";
/**
* 2020-06-09T09:00:00.000+08:00[Asia/Shanghai], keep the zone id to avoid losing it after deserialize
*
* <p>The offset uses ZZZZZ and the year uses uuuu, unlike the patterns above:
* ZZZ drops the seconds of the offset, and on deserialize the offset overrides the zone when the
* instant is computed, so any zone that still had a sub-minute offset comes back shifted
* (Africa/Monrovia was -00:44:30 until 1972, Instant.EPOCH drifts 30s there) and the shift
* accumulates over record-replay-record. yyyy is year-of-era, it turns a year before 1 AD into a
* positive one and can not write LocalDateTime.MIN back.
*/
public static final String SIMPLE_DATE_FORMAT_MILLIS_WITH_ZONE_ID = "uuuu-MM-dd'T'HH:mm:ss.SSSZZZZZ'['VV']'";
public static final String SIMPLE_DATE_FORMAT_NANOSECOND_WITH_ZONE_ID = "uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSSZZZZZ'['VV']'";
/**
* yyyy-MM-dd
*/
Expand All @@ -40,11 +52,14 @@ private TimePatternConstants() {

public static String localTimeFormat = SHORT_TIME_FORMAT_MILLISECOND;

public static String zonedDateTimeFormat = SIMPLE_DATE_FORMAT_MILLIS_WITH_ZONE_ID;

static {
if (JdkUtils.isJdk11OrHigher()) {
localDateTimeFormat = SIMPLE_DATE_FORMAT_NANOSECOND;
localTimeFormat = SHORT_TIME_FORMAT_NANOSECOND;
localTimeZeroSecondTimeRequest = "00.000000000";
zonedDateTimeFormat = SIMPLE_DATE_FORMAT_NANOSECOND_WITH_ZONE_ID;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ void serialize() throws Throwable {
assertEquals(testInfo.getLocalDate(), zeroSecondTimeTestInfo.getLocalDate());
assertEquals(testInfo.getLocalTime(), zeroSecondTimeTestInfo.getLocalTime());
assertEquals(testInfo.getOffsetDateTime(), zeroSecondTimeTestInfo.getOffsetDateTime());
assertEquals(testInfo.getZonedDateTime(), zeroSecondTimeTestInfo.getZonedDateTime());
assertEquals(testInfo.getTimeZone1(), zeroSecondTimeTestInfo.getTimeZone1());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,26 @@
import com.google.gson.internal.LinkedTreeMap;
import io.arex.agent.bootstrap.internal.Pair;
import io.arex.foundation.serializer.gson.GsonSerializer;
import io.arex.foundation.serializer.jackson.JacksonSerializer;
import io.arex.inst.runtime.util.TypeUtil;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;

import java.lang.reflect.Type;
import java.sql.Time;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Map;

import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

class GsonSerializerTest {

Expand All @@ -43,6 +52,50 @@ public void testSqlTime() throws InterruptedException {
assertEquals(expectedJson, actualJson);
}

/**
* https://github.com/arextest/arex-agent-java/issues/588
*/
@ParameterizedTest
@MethodSource("zonedDateTimeCase")
public void testZonedDateTime(ZonedDateTime expected) {
String json = GsonSerializer.INSTANCE.serialize(expected);
ZonedDateTime actualResult = GsonSerializer.INSTANCE.deserialize(json, ZonedDateTime.class);
assertEquals(expected, actualResult);
// the zone id should not be replaced by the zone offset
assertEquals(expected.getZone(), actualResult.getZone());
// the offset keeps the local time repeated in a dst overlap distinguishable
assertEquals(expected.getOffset(), actualResult.getOffset());
assertEquals(json, GsonSerializer.INSTANCE.serialize(actualResult));
}

static Stream<Arguments> zonedDateTimeCase() {
// 2025-11-02T01:30 of America/New_York happens twice, once with -04:00 and once with -05:00
ZonedDateTime dstOverlap = ZonedDateTime.of(2025, 11, 2, 1, 30, 0, 0, ZoneId.of("America/New_York"));
return Stream.of(
Arguments.arguments(ZonedDateTime.of(2020, 6, 9, 9, 0, 0, 123456789, ZoneId.of("Asia/Shanghai"))),
Arguments.arguments(ZonedDateTime.now(ZoneId.of("Asia/Shanghai"))),
Arguments.arguments(ZonedDateTime.now(ZoneOffset.UTC)),
Arguments.arguments(ZonedDateTime.now(ZoneId.of("GMT-01:00"))),
Arguments.arguments(dstOverlap.withEarlierOffsetAtOverlap()),
Arguments.arguments(dstOverlap.withLaterOffsetAtOverlap()),
// zones that still had an offset with seconds, dropping them shifts the instant
Arguments.arguments(ZonedDateTime.of(1900, 1, 1, 12, 0, 0, 0, ZoneId.of("Asia/Shanghai"))),
Arguments.arguments(Instant.EPOCH.atZone(ZoneId.of("Africa/Monrovia"))),
Arguments.arguments(ZonedDateTime.of(2026, 1, 1, 9, 0, 0, 0, ZoneOffset.ofTotalSeconds(8 * 3600 + 30))),
// year of era would turn this into the year 100 AD
Arguments.arguments(ZonedDateTime.of(-100, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)));
}

/**
* both serializers must emit the same bytes, a case can be recorded with one and replayed with
* the other
*/
@Test
public void testZonedDateTimeSameFormatAsJackson() throws Throwable {
ZonedDateTime expected = ZonedDateTime.of(2020, 6, 9, 9, 0, 0, 123456789, ZoneId.of("Asia/Shanghai"));
assertEquals(JacksonSerializer.INSTANCE.serialize(expected), GsonSerializer.INSTANCE.serialize(expected));
}

@Test
public void testTimeSerializeAndDeserialize() throws Exception {
TimeTestInfo expectedTimeTest = new TimeTestInfo(LocalDateTime.now());
Expand Down Expand Up @@ -71,6 +124,7 @@ public void testTimeSerializeAndDeserialize() throws Exception {
assert expectedTimeTest.getDate().equals(deserializedTimeTest.getDate());

assert expectedTimeTest.getInstant().equals(deserializedTimeTest.getInstant());
assert expectedTimeTest.getZonedDateTime().equals(deserializedTimeTest.getZonedDateTime());

assert expectedTimeTest.getJodaLocalDate().equals(deserializedTimeTest.getJodaLocalDate());
assert expectedTimeTest.getJodaLocalTime().equals(deserializedTimeTest.getJodaLocalTime());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ void serialize() throws Throwable {
assertEquals(testInfo.getLocalDate(), zeroSecondTimeTestInfo.getLocalDate());
assertEquals(testInfo.getLocalTime(), zeroSecondTimeTestInfo.getLocalTime());
assertEquals(testInfo.getOffsetDateTime(), zeroSecondTimeTestInfo.getOffsetDateTime());
assertEquals(testInfo.getZonedDateTime(), zeroSecondTimeTestInfo.getZonedDateTime());
assertEquals(testInfo.getTimeZone1(), zeroSecondTimeTestInfo.getTimeZone1());
}

Expand Down
Loading
Loading