美文网首页Spring-Boot开发这点事程序员
SpringBoot 解决时区问题之DateFormat

SpringBoot 解决时区问题之DateFormat

作者: 虎小伟 | 来源:发表于2019-03-14 20:09 被阅读13次

    背景

    今天在开发中发现通过spring的mybatis查出的时间比DB中的时间少一天,直接执行MySql的sql发现和DB中的时间一样,经过对比发现是时区问题。因为DB服务器是日本时区,取到的dto日期为中国时区,少了一个小时,但是因为date类型只取到天数,故少了一天。

    解决

    参考文章(来自[Architect剑]):https://www.cnblogs.com/yi1036943655/p/9854887.html

    1、在启动类加上
    
    @PostConstruct
    
    void setDefaultTimezone() {
     TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
    } 
    2、在application.properties加上
     ## json setting
    spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    spring.jackson.time-zone=Asia/Shanghai 
     
    3、在启动类 启动run方法里加上
    public static void main(String[] args) {
       TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
      SpringApplication.run(BaseMicroServiceApplication.class, args);
    }
    

    安装上面的方法试了之后果然得到解决,后台数据显示到前台页面没有问题了。

    新问题

    但是前台的date类型向后台传送时发生异常:

    JSON parse error: Can not deserialize value of type java.util.Date from String "2018-03-07T16:18:35Z": not a valid representation (error: Failed to parse Date value '2018-03-07 16:18:35': Can not parse date "2018-03-07 16:18:35Z": while it seems to fit format 'yyyy-MM-dd HH:mm:ss'
    

    对策

    方案1,这里有个简单的方法,但是要每个对象都注解,果断选择下面一种。
    https://blog.csdn.net/zhangminemail/article/details/83188522
    方案2,好像是前后台的日期格式不一样,通过搜索找到如下解决方法:
    原文地址(来自[黄雄杰]):https://blog.csdn.net/qq906627950/article/details/79503801
    这篇文章分析了发生错误的原因,可谓是刨根问底。
    但是安装作者的代码加上去之后,启动时出现空指针异常,原因是setTimeZone时没有calendar对象。

    Caused by: java.lang.NullPointerException: null
        at jp.co.irep.report.backend.app.config.MyDateFormat.setTimeZone(MyDateFormat.java:56)
        at com.fasterxml.jackson.databind.cfg.BaseSettings._force(BaseSettings.java:356)
        at com.fasterxml.jackson.databind.cfg.BaseSettings.withDateFormat(BaseSettings.java:226)
        at com.fasterxml.jackson.databind.cfg.MapperConfigBase.with(MapperConfigBase.java:489)
        at com.fasterxml.jackson.databind.ObjectMapper.setDateFormat(ObjectMapper.java:1855)
    

    通过文章下面的评论又找到了另一篇对应方案:
    https://blog.csdn.net/xtj332/article/details/84570181
    这位作者([freewind])时把dateFormat的属性复制给了新的对象dateFormat1,然后序列化时将null都转成了“”。

    这样楼主的问题时解决了,但是我这里又有新问题

    异常:

    Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `java.util.ArrayList` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('')
     at [Source: (PushbackInputStream); line: 1, column: 946] (through reference chain: jp.co.irep.report.backend.dto.report008.Report008FormDto["rptBlckIndexList"])
        at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
    

    原因是,我从前台向后台传了一个list 没数据时是null,这一转成“”完蛋了。

    知道了问题所在就好修改了,我在[黄雄杰]的基础上稍微做了改善。

        public MyDateFormat(DateFormat dateFormat) {
            this.dateFormat = dateFormat;
            this.calendar = dateFormat.getCalendar();
        }
    

    既然calendar属性是null,那我就在构造函数里给他个对象,😄
    这样一来,启动的问题解决了。
    但是跑起来还是同样的format转换问题,看来[黄雄杰]的修改并没有解决我的问题。

    最后经过研究发现,前台传给后台的json中,日期格式是标准时间“yyyy-MM-dd'T'HH:mm:ss.SSS'Z'”
    而后台已经被我们改成了非标准时间的“yyyy-MM-dd HH:mm:ss”,
    如下,前台的JSON.stringify方法转json时默认使用标准时间:

    http.interceptors.request.use(
      config => {
        if (config.method === 'post' ||
          config.method === 'put' ||
          config.method === 'delete') {
          config.data = JSON.stringify(config.data)
        }
        showFullScreenLoading()
        return config
      },
      error => {
        Message({
          showClose: true,
          message: error,
          type: 'error.data.error.message'
        })
        return Promise.reject(error.data.error.message)
      }
    )
    

    既然如此,那我后台接收时按标准时间“yyyy-MM-dd'T'HH:mm:ss.SSS'Z'”来接收,然后转成spring的时间即可。最终在[黄雄杰]的代码基础上,修改了MyDateFormat的parse方法得以解决。

    全部代码记录如下:
    application.properties:
    spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    spring.jackson.time-zone=Japan
    
    启动类:
        @PostConstruct
        void started() {
            TimeZone.setDefault(TimeZone.getTimeZone("Japan"));
        }
    
        public static void main(String[] args) {
            TimeZone.setDefault(TimeZone.getTimeZone("Japan"));
            SpringApplication.run(MainApplication.class, args);
        }
    
    自己的DateFormat类:
    import org.apache.commons.lang.StringUtils;
    
    import java.text.DateFormat;
    import java.text.FieldPosition;
    import java.text.ParseException;
    import java.text.ParsePosition;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.TimeZone;
    
    public class MyDateFormat extends DateFormat {
    
        private DateFormat dateFormat;
    
        private SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        private SimpleDateFormat format2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
    
        public MyDateFormat(DateFormat dateFormat) {
            this.dateFormat = dateFormat;
            this.calendar = dateFormat.getCalendar();
        }
    
        @Override
        public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
            return dateFormat.format(date, toAppendTo, fieldPosition);
        }
    
        @Override
        public Date parse(String source, ParsePosition pos) {
            Date date = null;
            try {
                date = format1.parse(source, pos);
            } catch (Exception e) {
                if (StringUtils.contains(source, "T")) {
                    format2.setTimeZone(TimeZone.getTimeZone("UTC"));
                    date = format1.parse(format1.format(format2.parse(source, pos)), pos);
                } else {
                    date = dateFormat.parse(source, pos);
                }
            }
            return date;
        }
    
        @Override
        public Date parse(String source) throws ParseException {
            Date date = null;
            try {
                date = format1.parse(source);
            } catch (Exception e) {
                if (StringUtils.contains(source, "T")) {
                    format2.setTimeZone(TimeZone.getTimeZone("UTC"));
                    date = format1.parse(format1.format(format2.parse(source)));
                } else {
                    date = dateFormat.parse(source);
                }
            }
            return date;
        }
    
        @Override
        public Object clone() {
            Object format = dateFormat.clone();
            return new MyDateFormat((DateFormat) format);
        }
    }
    
    配置DateFormat的config类:
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
    import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
    
    import java.text.DateFormat;
    
    @Configuration
    public class JacksonConfig {
    
        @Autowired
        private Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder;
    
        @Bean
        public MappingJackson2HttpMessageConverter MappingJsonpHttpMessageConverter() {
    
            ObjectMapper mapper = jackson2ObjectMapperBuilder.build();
    
            DateFormat dateFormat = mapper.getDateFormat();
            mapper.setDateFormat(new MyDateFormat(dateFormat));
    
            MappingJackson2HttpMessageConverter mappingJsonpHttpMessageConverter = new MappingJackson2HttpMessageConverter(mapper);
            return mappingJsonpHttpMessageConverter;
        }
    }
    

    到此问题得到完美解决,谢谢 博客园的[Architect剑],CSDN的[黄雄杰]以及[freewind]给我的启发。

    如果这篇文章对你有帮助,可以分享给更多的人看到,谢谢。

    最后打个广告,这是作者的微信公众号,欢迎关注。

    相关文章

      网友评论

        本文标题:SpringBoot 解决时区问题之DateFormat

        本文链接:https://www.haomeiwen.com/subject/jqycmqtx.html