解决LocalDateTime 时间格式中间带’T’返回的问题(小心踩坑)

解决LocalDateTime 时间格式中间带’T’返回的问题

前言:在一次工作开发过程中,LocalDateTime 类型的字段返回给前端带着T,然后被测试无情的提了一个bug,然后只能使用百度大法了,结果发现网上全是一堆相同的答案,且是无效无用的,只能自己实践整理一版有效的使用出来。

PS:要看自己的项目是数据序列化是不是用fastJson序列化(很重要,因为这个导致很多正常有效的都变无效了,我就是踩这个坑)

首先我们先了解Date和LocalDateTime 的源码返回格式

下面是Date的toString方法的源码,里面返回的字符格式就是日常使用的

public String toString() {
    // "EEE MMM dd HH:mm:ss zzz yyyy";
    BaseCalendar.Date date = normalize();
    StringBuilder sb = new StringBuilder(28);
    int index = date.getDayOfWeek();
    if (index == BaseCalendar.SUNDAY) {
        index = 8;
    }
    convertToAbbr(sb, wtb[index]).append(' ');                        // EEE
    convertToAbbr(sb, wtb[date.getMonth() - 1 + 2 + 7]).append(' ');  // MMM
    CalendarUtils.sprintf0d(sb, date.getDayOfMonth(), 2).append(' '); // dd

    CalendarUtils.sprintf0d(sb, date.getHours(), 2).append(':');   // HH
    CalendarUtils.sprintf0d(sb, date.getMinutes(), 2).append(':'); // mm
    CalendarUtils.sprintf0d(sb, date.getSeconds(), 2).append(' '); // ss
    TimeZone zi = date.getZone();
    if (zi != null) {
        sb.append(zi.getDisplayName(date.isDaylightTime(), TimeZone.SHORT, Locale.US)); // zzz
    } else {
        sb.append("GMT");
    }
    sb.append(' ').append(date.getYear());  // yyyy
    return sb.toString();
}

LocalDateTime的toString方法的源码,会把T放在年月日和时分秒的中间,所以会出现带’T’的字符串

@Override
public String toString() {
    return date.toString() + 'T' + time.toString();
}

所以如果,后端想返回给前端不带’T’的时间格式,想省事就直接把LocalDateTime类型改为Date类型,然后在字段上加上注解即可

@JsonFormat(pattern = “yyyy-MM-dd HH:mm:ss”,timezone=“GMT+8”)

但是,这不是我们想要的,如果我们就是想用LocalDateTime类型字段返回给前端,那应该怎么办呢。

一、使用字符串替(不推荐,很low)

前面我们可以知道,LocalDateTime源码是会自动把T加上去的,所以我们要做的比较简单,就是把T替换成空格即可

二、使用注解(处理局部实体对象返回)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
三、使用全局配置统一处理(对日期数据序列化和反序列化处理)
方式一:
import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

@Configuration
public class LocalDateTimeConfig {


    @Bean
    @Primary
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
//日期转字符串
        return builder -> builder.serializerByType(LocalDate.class,new LocalDateTimeSerializer(DatePattern.NORM_DATE_FORMATTER))
                .serializerByType(LocalDateTime.class,new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMATTER))
                .serializerByType(LocalTime.class,new LocalDateTimeSerializer(DatePattern.NORM_TIME_FORMATTER))
//字符串转日期
                .deserializerByType(LocalDateTime.class,new LocalDateTimeDeserializer(DatePattern.NORM_DATETIME_FORMATTER))
                .deserializerByType(LocalDate.class,new LocalDateDeserializer(DatePattern.NORM_DATE_FORMATTER))
                .deserializerByType(LocalTime.class,new LocalTimeDeserializer(DatePattern.NORM_TIME_FORMATTER));
    }
}

方式二:

import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;


@Configuration
public class LocalDateTimeConfig {




    @Bean
    public LocalDateTimeSerializer localDateTimeSerializer(){
        return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
        return e -> e.serializerByType(LocalDateTime.class,localDateTimeSerializer());
    }
}
四、总结(很重要,踩坑)

在我们实际生产项目中,后端返回给前端的数据都是做统一的格式化处理,例如用到fastjson进行格式化,此时有可能会导致上面的一些方式失效,例如下面就是用JSON.toJSONString将localDateTime日期转换,

public static String toString(Object data) {
    return JSON.toJSONString(data, features);
}
/**
 * 可以看到fastJson.toJSONString的序列化LocalDateTime的格式是带T的
 */
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
        ...

            if (fieldType == LocalDateTime.class) {
                int mask = SerializerFeature.UseISO8601DateFormat.getMask();
                LocalDateTime dateTime = (LocalDateTime)object;
                String format = serializer.getDateFormatPattern();
                if (format == null) {
                    if ((features & mask) == 0 && !serializer.isEnabled(SerializerFeature.UseISO8601DateFormat)) {
                        int nano = dateTime.getNano();
                        if (nano == 0) {
                            format = "yyyy-MM-dd'T'HH:mm:ss";
                        } else if (nano % 1000000 == 0) {
                            format = "yyyy-MM-dd'T'HH:mm:ss.SSS";
                        } else {
                            format = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS";
                        }
                    } else {
                        format = "yyyy-MM-dd'T'HH:mm:ss";
                    }
                }
                ....
        }

    }

如果如果上面的方法不生效的话,可以用下面的方法尝试下

使用@JSONField代替@JsonFormat

@JSONField(format = "yyyy-MM-dd HH:mm:ss")