// 转换:Long 毫秒时间戳 → LocalDate
private LocalDate toLocalDate(Long ts, ZoneId zoneId) {
return ts == null ? null : Instant.ofEpochMilli(ts).atZone(zoneId).toLocalDate();
}
// 转换:LocalDate → Long 毫秒时间戳(当天 00:00)
private Long toEpochMillis(LocalDate date, ZoneId zoneId) {
return date == null ? null : date.atStartOfDay(zoneId).toInstant().toEpochMilli();
}
让“字符串时间戳”自动转换到 LocalDate/LocalDateTime。
注意:LocalDate 必须选定 ZoneId 才能从时间戳还原到日期。
示例:
// 用于 ?ts=1730035200000 绑定到 LocalDateTime/LocalDate
@Component
public class EpochMilliToLocalDateTimeConverter implements Converter<String, LocalDateTime> {
@Override
public LocalDateTime convert(String source) {
long millis = Long.parseLong(source);
ZoneId zoneId = resolveZoneId(); // 从用户/租户/请求头解析
return Instant.ofEpochMilli(millis).atZone(zoneId).toLocalDateTime();
}
}
@Component
public class EpochMilliToLocalDateConverter implements Converter<String, LocalDate> {
@Override
public LocalDate convert(String source) {
long millis = Long.parseLong(source);
ZoneId zoneId = resolveZoneId();
return Instant.ofEpochMilli(millis).atZone(zoneId).toLocalDate();
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new EpochMilliToLocalDateTimeConverter());
registry.addConverter(new EpochMilliToLocalDateConverter());
}
}
可以通过两种方式实现数值时间戳与 LocalDate/LocalDateTime 的映射:
下面给出“全局自定义序列化/反序列化”的落地代码,默认使用 Asia/Shanghai,如需从租户/用户时区解析,可把 ZoneId 的来源替换为你们现有的时区解析逻辑或请求头 X-Timezone。
为什么新增这些代码
package top.open1024.mewtwo.calendar.config;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.ZoneId;
@Configuration
public class JacksonConfig {
@Bean
public ZoneId defaultZoneId() {
return ZoneId.of("Asia/Shanghai");
}
@Bean
public Jackson2ObjectMapperBuilderCustomizer javaTimeCustomizer(ZoneId defaultZoneId) {
return builder -> {
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(java.time.LocalDateTime.class,
new top.open1024.mewtwo.calendar.config.time.EpochMillisLocalDateTimeDeserializer(defaultZoneId));
javaTimeModule.addDeserializer(java.time.LocalDate.class,
new top.open1024.mewtwo.calendar.config.time.EpochMillisLocalDateDeserializer(defaultZoneId));
javaTimeModule.addSerializer(java.time.LocalDateTime.class,
new top.open1024.mewtwo.calendar.config.time.EpochMillisLocalDateTimeSerializer(defaultZoneId));
javaTimeModule.addSerializer(java.time.LocalDate.class,
new top.open1024.mewtwo.calendar.config.time.EpochMillisLocalDateSerializer(defaultZoneId));
builder.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
builder.modules(new Module[]{javaTimeModule});
};
}
}
package top.open1024.mewtwo.calendar.config.time;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
public class EpochMillisLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
private final ZoneId zoneId;
public EpochMillisLocalDateTimeDeserializer(ZoneId zoneId) {
this.zoneId = zoneId;
}
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
long millis = p.getLongValue();
return LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), zoneId);
}
}
package top.open1024.mewtwo.calendar.config.time;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
public class EpochMillisLocalDateDeserializer extends JsonDeserializer<LocalDate> {
private final ZoneId zoneId;
public EpochMillisLocalDateDeserializer(ZoneId zoneId) {
this.zoneId = zoneId;
}
@Override
public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
long millis = p.getLongValue();
return Instant.ofEpochMilli(millis).atZone(zoneId).toLocalDate();
}
}
package top.open1024.mewtwo.calendar.config.time;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.time.LocalDate;
import java.time.ZoneId;
public class EpochMillisLocalDateSerializer extends JsonSerializer<LocalDate> {
private final ZoneId zoneId;
public EpochMillisLocalDateSerializer(ZoneId zoneId) {
this.zoneId = zoneId;
}
@Override
public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
long millis = value.atStartOfDay(zoneId).toInstant().toEpochMilli();
gen.writeNumber(millis);
}
}
package top.open1024.mewtwo.calendar.config.time;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;
public class EpochMillisLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
private final ZoneId zoneId;
public EpochMillisLocalDateTimeSerializer(ZoneId zoneId) {
this.zoneId = zoneId;
}
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
long millis = value.atZone(zoneId).toInstant().toEpochMilli();
gen.writeNumber(millis);
}
}
package top.open1024.mewtwo.calendar.config.time;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
/**
* 将毫秒时间戳反序列化为 LocalDate(按 Asia/Shanghai 计算到当天)
*/
public class EpochMillisLocalDateDeserializer extends JsonDeserializer<LocalDate> {
private static final ZoneId DEFAULT_ZONE = ZoneId.of("Asia/Shanghai");
@Override
public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
long millis = p.getLongValue();
return Instant.ofEpochMilli(millis).atZone(DEFAULT_ZONE).toLocalDate();
}
}
package top.open1024.mewtwo.calendar.config.time;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.time.LocalDate;
import java.time.ZoneId;
/**
* 将 LocalDate 序列化为毫秒时间戳(当天 00:00 的 epoch millis,按 Asia/Shanghai)
*/
public class EpochMillisLocalDateSerializer extends JsonSerializer<LocalDate> {
private static final ZoneId DEFAULT_ZONE = ZoneId.of("Asia/Shanghai");
@Override
public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value == null) {
gen.writeNull();
} else {
long millis = value.atStartOfDay(DEFAULT_ZONE).toInstant().toEpochMilli();
gen.writeNumber(millis);
}
}
}
使用方式
/**
* 需要操作的实例日期(仅当 updateType 为 single 或 future 时需要)
*/
@Schema(description = "需要操作的实例日期")
// Deleted:@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@JsonDeserialize(using = EpochMillisLocalDateDeserializer.class)
private LocalDate occurrenceDate;
/**
* 开始日期
*/
// Deleted:@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@JsonDeserialize(using = EpochMillisLocalDateDeserializer.class)
private LocalDate startDate;
/**
* 结束日期(重复餐食)
*/
// Deleted:@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@JsonDeserialize(using = EpochMillisLocalDateDeserializer.class)
private LocalDate untilDate;
@Schema(description = "实际发生日期")
// Deleted:@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@JsonSerialize(using = EpochMillisLocalDateSerializer.class)
private LocalDate occurrenceDate;