Spring接收到的时间和前端相差8小时-创新互联

文章目录
    • 1 缘起
    • 2 问题原因分析
    • 3 解决办法
    • 4 扩展

创新互联公司主要从事成都网站建设、成都做网站、网页设计、企业做网站、公司建网站等业务。立足成都服务玉龙,10多年网站建设经验,价格优惠、服务专业,欢迎来电咨询建站服务:189820811081 缘起

  后端使用Spring接收参数,用@ResquestBody接收请求入参,前端传参一日期类型的参数为“2022-12-30”,后端接收到以后打印的值为“2022-12-30 08:00:00”,后端比前端提前8小时。
  后端使用@RestController,给前端返回时间类型的参数时,前端接收到的时间比后端滞后8小时。

2 问题原因分析

  这种情况下,后端认为前端传参或者后端返回给前端的参数都是JSON格式,所以使用解析JSON的。后端在解析前端传参时,使用的参数转换器是MappingJackson2HttpMessageConverter,默认时区是UTC,而我们一般的机器中的JVM 是东八区,两个时区不一样,导致这种情况下日期类型的转换会做一定的处理。故,本质上取决于处理时间类型的处理器使用的时区和JVM时区是否一致。跟踪代码,发现最终使用的是StdDateFormat类中的parse方法进行解析,代码如下:
关键类:
com.fasterxml.jackson.databind.util.StdDateFormat#parse(java.lang.String, java.text.ParsePosition)

public class StdDateFormat extends DateFormat {@Override
    public Date parse(String dateStr) throws ParseException
    {dateStr = dateStr.trim();
        ParsePosition pos = new ParsePosition(0);
        Date dt = _parseDate(dateStr, pos);
        if (dt != null) {return dt;
        }
        StringBuilder sb = new StringBuilder();
        for (String f : ALL_FORMATS) {if (sb.length() >0) {sb.append("\", \"");
            } else {sb.append('"');
            }
            sb.append(f);
        }
        sb.append('"');
        throw new ParseException
            (String.format("Cannot parse date \"%s\": not compatible with any of standard forms (%s)",
                           dateStr, sb.toString()), pos.getErrorIndex());
    }

	protected Date _parseDate(String dateStr, ParsePosition pos) throws ParseException
    {if (looksLikeISO8601(dateStr)) {// also includes "plain"
            return parseAsISO8601(dateStr, pos);
        }
        // Also consider "stringified" simple time stamp
        int i = dateStr.length();
        while (--i >= 0) {char ch = dateStr.charAt(i);
            if (ch< '0' || ch >'9') {// 07-Aug-2013, tatu: And [databind#267] points out that negative numbers should also work
                if (i >0 || ch != '-') {break;
                }
            }
        }
        if ((i< 0)
            // let's just assume negative numbers are fine (can't be RFC-1123 anyway); check length for positive
                && (dateStr.charAt(0) == '-' || NumberInput.inLongRange(dateStr, false))) {return _parseDateFromLong(dateStr, pos);
        }
        // Otherwise, fall back to using RFC 1123. NOTE: call will NOT throw, just returns `null`
        return parseAsRFC1123(dateStr, pos);
    }

	protected Date parseAsISO8601(String dateStr, ParsePosition pos)
        throws ParseException
    {try {return _parseAsISO8601(dateStr, pos);
        } catch (IllegalArgumentException e) {throw new ParseException(String.format("Cannot parse date \"%s\", problem: %s",
                    dateStr, e.getMessage()),
                    pos.getErrorIndex());
        }
    }

	protected Date _parseAsISO8601(String dateStr, ParsePosition bogus)
        throws IllegalArgumentException, ParseException
    {final int totalLen = dateStr.length();
        // actually, one short-cut: if we end with "Z", must be UTC
        TimeZone tz = DEFAULT_TIMEZONE;
        if ((_timezone != null) && ('Z' != dateStr.charAt(totalLen-1))) {tz = _timezone;
        }
        Calendar cal = _getCalendar(tz);
        cal.clear();
        String formatStr;
        // 对于只有年月日的字符串,走这个分支
        if (totalLen<= 10) {Matcher m = PATTERN_PLAIN.matcher(dateStr);
            if (m.matches()) {int year = _parse4D(dateStr, 0);
                int month = _parse2D(dateStr, 5)-1;
                int day = _parse2D(dateStr, 8);

                cal.set(year, month, day, 0, 0, 0);
                cal.set(Calendar.MILLISECOND, 0);
                return cal.getTime();
            }
            formatStr = DATE_FORMAT_STR_PLAIN;
        } else {Matcher m = PATTERN_ISO8601.matcher(dateStr);
            if (m.matches()) {// Important! START with optional time zone; otherwise Calendar will explode
                
                int start = m.start(2);
                int end = m.end(2);
                int len = end-start;
                if (len >1) {// 0 ->none, 1 ->'Z'
                    // NOTE: first char is sign; then 2 digits, then optional colon, optional 2 digits
                    int offsetSecs = _parse2D(dateStr, start+1) * 3600; // hours
                    if (len >= 5) {offsetSecs += _parse2D(dateStr, end-2) * 60; // minutes
                    }
                    if (dateStr.charAt(start) == '-') {offsetSecs *= -1000;
                    } else {offsetSecs *= 1000;
                    }
                    cal.set(Calendar.ZONE_OFFSET, offsetSecs);
                    // 23-Jun-2017, tatu: Not sure why, but this appears to be needed too:
                    cal.set(Calendar.DST_OFFSET, 0);
                }
                
                int year = _parse4D(dateStr, 0);
                int month = _parse2D(dateStr, 5)-1;
                int day = _parse2D(dateStr, 8);

                // So: 10 chars for date, then `T`, so starts at 11
                int hour = _parse2D(dateStr, 11);
                int minute = _parse2D(dateStr, 14);

                // Seconds are actually optional... so
                int seconds;
                if ((totalLen >16) && dateStr.charAt(16) == ':') {seconds = _parse2D(dateStr, 17);
                } else {seconds = 0;
                }
                cal.set(year, month, day, hour, minute, seconds);

                // Optional milliseconds
                start = m.start(1) + 1;
                end = m.end(1);
                int msecs = 0;
                if (start >= end) {// no fractional
                    cal.set(Calendar.MILLISECOND, 0);
                } else {// first char is '.', but rest....
                    msecs = 0;
                    final int fractLen = end-start;
                    switch (fractLen) {default: // [databind#1745] Allow longer fractions... for now, cap at nanoseconds tho

                        if (fractLen >9) {// only allow up to nanos
                            throw new ParseException(String.format(
"Cannot parse date \"%s\": invalid fractional seconds '%s'; can use at most 9 digits",
                                       dateStr, m.group(1).substring(1)
                                       ), start);
                        }
                        // fall through
                    case 3:
                        msecs += (dateStr.charAt(start+2) - '0');
                    case 2:
                        msecs += 10 * (dateStr.charAt(start+1) - '0');
                    case 1:
                        msecs += 100 * (dateStr.charAt(start) - '0');
                        break;
                    case 0:
                        break;
                    }
                    cal.set(Calendar.MILLISECOND, msecs);
                }
                return cal.getTime();
            }
            formatStr = DATE_FORMAT_STR_ISO8601;
        }

        throw new ParseException
        (String.format("Cannot parse date \"%s\": while it seems to fit format '%s', parsing fails (leniency? %s)",
                       dateStr, formatStr, _lenient),
                // [databind#1742]: Might be able to give actual location, some day, but for now
                //  we can't give anything more indicative
                0);
    }
}
3 解决办法

第一种:在指定字段上加@JSonFormat注解并设置时区

@JsonFormat(timezone = "GMT+8")
private Date date;

第二种:若需要加注解的字段太多,给每个字段加注解也不是一种好方法。这时,可以在配置文件application.properties中配置时区,如下:

spring.jackson.time-zone=GMT+8
4 扩展

  最后,推荐两篇不错的博文:
1)SpringMVC如何正确接收时间
2)Jackson序列化
3)彻底弄透Java处理GMT-UTC日期时间

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


名称栏目:Spring接收到的时间和前端相差8小时-创新互联
网站路径:http://hbruida.cn/article/jhjep.html