diff --git a/plusone-commons/pom.xml b/plusone-commons/pom.xml
index 223023b..c238688 100644
--- a/plusone-commons/pom.xml
+++ b/plusone-commons/pom.xml
@@ -55,6 +55,13 @@
true
+
+
+ com.google.code.gson
+ gson
+ true
+
+
@@ -111,12 +118,6 @@
jackson-datatype-jsr310
test
-
-
- com.google.code.gson
- gson
- test
-
diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/gson/LocalDateTimeTypeAdapter.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/gson/LocalDateTimeTypeAdapter.java
new file mode 100644
index 0000000..317f404
--- /dev/null
+++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/gson/LocalDateTimeTypeAdapter.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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 xyz.zhouxy.plusone.commons.gson;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import xyz.zhouxy.plusone.commons.util.AssertTools;
+
+/**
+ * {@code LocalDateTime} 的 {@code TypeAdapter},
+ * 用于 Gson 对 {@code LocalDateTime} 进行相互转换。
+ *
+ * @author ZhouXY
+ * @since 1.1.0
+ * @see TypeAdapter
+ * @see com.google.gson.GsonBuilder
+ */
+public class LocalDateTimeTypeAdapter extends TypeAdapter {
+
+ private final DateTimeFormatter dateTimeFormatter;
+
+ /**
+ * 默认构造函数,
+ * 使用 {@link DateTimeFormatter#ISO_LOCAL_DATE_TIME} 进行 {@link LocalDateTime} 的序列化与反序列化。
+ */
+ public LocalDateTimeTypeAdapter() {
+ this.dateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
+ }
+
+ /**
+ * 构造函数,
+ * 使用传入的 {@link DateTimeFormatter} 进行 {@link LocalDateTime} 的序列化与反序列化。
+ *
+ * @param formatter 用于序列化 {@link LocalDateTime} 的格式化器,不可为 {@code null}。
+ */
+ public LocalDateTimeTypeAdapter(DateTimeFormatter formatter) {
+ AssertTools.checkArgumentNotNull(formatter, "formatter can not be null.");
+ this.dateTimeFormatter = formatter;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void write(JsonWriter out, LocalDateTime value) throws IOException {
+ out.value(dateTimeFormatter.format(value));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public LocalDateTime read(JsonReader in) throws IOException {
+ return LocalDateTime.parse(in.nextString(), dateTimeFormatter);
+ }
+}
diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/gson/LocalDateTypeAdapter.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/gson/LocalDateTypeAdapter.java
new file mode 100644
index 0000000..5a42ad3
--- /dev/null
+++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/gson/LocalDateTypeAdapter.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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 xyz.zhouxy.plusone.commons.gson;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import xyz.zhouxy.plusone.commons.util.AssertTools;
+
+/**
+ * {@code LocalDate} 的 {@code TypeAdapter},
+ * 用于 Gson 对 {@code LocalDate} 进行相互转换。
+ *
+ * @author ZhouXY
+ * @since 1.1.0
+ * @see TypeAdapter
+ * @see com.google.gson.GsonBuilder
+ */
+public class LocalDateTypeAdapter extends TypeAdapter {
+
+ private final DateTimeFormatter dateTimeFormatter;
+
+ /**
+ * 默认构造函数,
+ * 使用 {@link DateTimeFormatter#ISO_LOCAL_DATE} 进行 {@link LocalDate} 的序列化与反序列化。
+ */
+ public LocalDateTypeAdapter() {
+ this.dateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE;
+ }
+
+ /**
+ * 构造函数,
+ * 使用传入的 {@link DateTimeFormatter} 进行 {@link LocalDate} 的序列化与反序列化。
+ *
+ * @param formatter 用于序列化 {@link LocalDate} 的格式化器,不可为 {@code null}。
+ */
+ public LocalDateTypeAdapter(DateTimeFormatter formatter) {
+ AssertTools.checkArgumentNotNull(formatter, "formatter can not be null.");
+ this.dateTimeFormatter = formatter;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void write(JsonWriter out, LocalDate value) throws IOException {
+ out.value(dateTimeFormatter.format(value));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public LocalDate read(JsonReader in) throws IOException {
+ return LocalDate.parse(in.nextString(), dateTimeFormatter);
+ }
+}
diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/gson/ZonedDateTimeTypeAdapter.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/gson/ZonedDateTimeTypeAdapter.java
new file mode 100644
index 0000000..7acd75e
--- /dev/null
+++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/gson/ZonedDateTimeTypeAdapter.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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 xyz.zhouxy.plusone.commons.gson;
+
+import java.io.IOException;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import xyz.zhouxy.plusone.commons.util.AssertTools;
+
+/**
+ * {@code ZonedDateTime} 的 {@code TypeAdapter},
+ * 用于 Gson 对 {@code ZonedDateTime} 进行相互转换。
+ *
+ * @author ZhouXY
+ * @since 1.1.0
+ * @see TypeAdapter
+ * @see com.google.gson.GsonBuilder
+ */
+public class ZonedDateTimeTypeAdapter extends TypeAdapter {
+
+ private final DateTimeFormatter dateTimeFormatter;
+
+ /**
+ * 默认构造函数,
+ * 使用 {@link DateTimeFormatter#ISO_ZONED_DATE_TIME} 进行 {@link ZonedDateTime} 的序列化与反序列化。
+ */
+ public ZonedDateTimeTypeAdapter() {
+ this.dateTimeFormatter = DateTimeFormatter.ISO_ZONED_DATE_TIME;
+ }
+
+ /**
+ * 构造函数,
+ * 使用传入的 {@link DateTimeFormatter} 进行 {@link ZonedDateTime} 的序列化与反序列化。
+ *
+ * @param formatter 用于序列化 {@link ZonedDateTime} 的格式化器,不可为 {@code null}。
+ */
+ public ZonedDateTimeTypeAdapter(DateTimeFormatter formatter) {
+ AssertTools.checkArgumentNotNull(formatter, "formatter can not be null.");
+ this.dateTimeFormatter = formatter;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void write(JsonWriter out, ZonedDateTime value) throws IOException {
+ out.value(dateTimeFormatter.format(value));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ZonedDateTime read(JsonReader in) throws IOException {
+ return ZonedDateTime.parse(in.nextString(), dateTimeFormatter);
+ }
+}
diff --git a/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/gson/GsonTypeAdapterTests.java b/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/gson/GsonTypeAdapterTests.java
new file mode 100644
index 0000000..b1d2022
--- /dev/null
+++ b/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/gson/GsonTypeAdapterTests.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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 xyz.zhouxy.plusone.commons.gson;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.temporal.ChronoField;
+
+import org.junit.jupiter.api.Test;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class GsonTypeAdapterTests {
+
+ final Gson gsonWithDefaultFormatter = new GsonBuilder()
+ .registerTypeAdapter(LocalDate.class, new LocalDateTypeAdapter().nullSafe())
+ .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeTypeAdapter().nullSafe())
+ .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeTypeAdapter().nullSafe())
+ .create();
+
+ final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
+ final DateTimeFormatter localDateTimeFormatter = new DateTimeFormatterBuilder()
+ .appendPattern("yyyy/MM/dd HH:mm:ss")
+ .appendValue(ChronoField.MILLI_OF_SECOND, 3)
+ .toFormatter();
+ final DateTimeFormatter zonedDateTimeFormatter = new DateTimeFormatterBuilder()
+ .appendPattern("yyyy/MM/dd HH:mm:ss")
+ .appendValue(ChronoField.MILLI_OF_SECOND, 3)
+ .appendZoneId()
+ .toFormatter();
+ final Gson gsonWithSpecifiedFormatter = new GsonBuilder()
+ .registerTypeAdapter(LocalDate.class, new LocalDateTypeAdapter(dateFormatter).nullSafe())
+ .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeTypeAdapter(localDateTimeFormatter).nullSafe())
+ .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeTypeAdapter(zonedDateTimeFormatter).nullSafe())
+ .create();
+
+ final LocalDate date = LocalDate.of(2025, 6, 6);
+ final LocalDateTime localDateTime = date.atTime(6, 6, 6, 666000000);
+ final ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("+08:00"));
+
+ @Test
+ void test_serialize_defaultFormatter() {
+ Foo foo = new Foo();
+ foo.localDate = date;
+ foo.localDateTime = localDateTime;
+ foo.zonedDateTime = zonedDateTime;
+
+ String json = String.format(
+ "{\"localDate\":\"%s\",\"localDateTime\":\"%s\",\"zonedDateTime\":\"%s\"}",
+ DateTimeFormatter.ISO_LOCAL_DATE.format(date),
+ DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(localDateTime),
+ DateTimeFormatter.ISO_ZONED_DATE_TIME.format(zonedDateTime)
+ );
+
+ assertEquals(json, gsonWithDefaultFormatter.toJson(foo));
+ }
+
+ @Test
+ void test_serialize_specifiedFormatter() {
+ Foo foo = new Foo();
+ foo.localDate = date;
+ foo.localDateTime = localDateTime;
+ foo.zonedDateTime = zonedDateTime;
+
+ String json = String.format(
+ "{\"localDate\":\"%s\",\"localDateTime\":\"%s\",\"zonedDateTime\":\"%s\"}",
+ dateFormatter.format(date),
+ localDateTimeFormatter.format(localDateTime),
+ zonedDateTimeFormatter.format(zonedDateTime)
+ );
+
+ assertEquals(json, gsonWithSpecifiedFormatter.toJson(foo));
+ }
+
+ @Test
+ void test_deserialize_defaultFormatter() {
+ String json = String.format(
+ "{\"localDate\":\"%s\",\"localDateTime\":\"%s\",\"zonedDateTime\":\"%s\"}",
+ DateTimeFormatter.ISO_LOCAL_DATE.format(date),
+ DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(localDateTime),
+ DateTimeFormatter.ISO_ZONED_DATE_TIME.format(zonedDateTime)
+ );
+ Foo foo = gsonWithDefaultFormatter.fromJson(json, Foo.class);
+ assertEquals(date, foo.localDate);
+ assertEquals(localDateTime, foo.localDateTime);
+ assertEquals(zonedDateTime, foo.zonedDateTime);
+ }
+
+ @Test
+ void test_deserialize_specifiedFormatter() {
+ String json = String.format(
+ "{\"localDate\":\"%s\",\"localDateTime\":\"%s\",\"zonedDateTime\":\"%s\"}",
+ dateFormatter.format(date),
+ localDateTimeFormatter.format(localDateTime),
+ zonedDateTimeFormatter.format(zonedDateTime)
+ );
+ Foo foo = gsonWithSpecifiedFormatter.fromJson(json, Foo.class);
+ assertEquals(date, foo.localDate);
+ assertEquals(localDateTime, foo.localDateTime);
+ assertEquals(zonedDateTime, foo.zonedDateTime);
+ }
+
+ static class Foo {
+ LocalDate localDate;
+ LocalDateTime localDateTime;
+ ZonedDateTime zonedDateTime;
+ }
+}