为啥活动提前结束了?记 Date 类型的一次踩坑!

2022-09-22 15:42:47 浏览数 (1)

线上问题

新功能的营销活动时间 23:59:59 才结束, 但是 16 点多发现页面显示"活动已结束"。

排查过程

HTTP 抓数据包

activityEndTime 为 1654761599000, 转化为 Date 为 2022-06-09 15:59:59

问题显而易见, 少了 8 个小时. 且不是前端的问题。

查询 DB 数据

活动结束时间是分为两个字段储存的:

代码语言:javascript复制
endDate (dateTime类型): 2022-06-09endTime(time类型): 23:59:59

java 类型都是用 Date 接收的. 暂时也没发现问题, 难道是 time 类型转 Date 类型导致的?

查看缓存

代码语言:javascript复制
endDate=1654704000000, endDate=57599000

换算了下没问题, 说明数据从 DB -> Java 中 Date 类型 -> JSON 序列化,链路是没问题的。

那只有一种可能:endDate 和 endTime 拼接成 activityEndTime 的时候出问题了

查看拼接逻辑

代码语言:javascript复制
activityEndTime = endDate.getTime()   endTime.getTime();

这太简单了, 难道会有问题?Debug 跟了一下, 果然结果有问题!

带着疑问点开了 Date.getTime() 方法:

代码语言:javascript复制
/** * Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT * represented by this <tt>Date</tt> object. * * @return  the number of milliseconds since January 1, 1970, 00:00:00 GMT *          represented by this date. */public long getTime() {    return getTimeImpl();}

也就是说 getTime() 返回的时间戳是相对于 0 时区的 January 1, 1970, 00:00:00 而言。

此时对于东 8 区而言, 是

代码语言:javascript复制
January 1, 1970, 08:00:00

验证了下: 

代码语言:javascript复制
57599000 / 3600000 = 15:59:59

代码语言:javascript复制
new Date(57599000L).toString()

结果是:

代码语言:javascript复制
Thu Jan 01 23:59:59 CST 1970

总结一句话:Date 是带时区的(跟着当前坐标的时区走),long 类型的时间戳相对于 0 时区

问题的原因

那这个地方为什么会少 8 小时呢?

因为案例里的 2022-06-09 和 23:59:59, 都是当前时区的时间, 即东 8 区的时间

代码语言:javascript复制
activityEndTime = endDate.getTime()   endTime.getTime()

调用 getTime() 会换算成 0 时区的时间戳时, 会向左偏移 8 小时(即减少),前端拿到相加的 activityEndTime 会向右偏移转化为东 8 区的 Date 类型。

但前面向左偏移了两次,后面只向右偏移了一次,所以不对等。导致多偏移了一次减少了 8 小时。

可以运行以下 Demo:

代码语言:javascript复制
Date date1 = new Date(0L); // 0时区的0点,东8区的8点Date date2 = new Date(3600*1000L); //0时区的1点,东8区的9点Date date3 = new Date(7200L*1000); //0时区的3点,东8区的11点
Date date4 = new Date(date1.getTime()   date2.getTime());Date date5 = new Date(date1.getTime()   date2.getTime()   date3.getTime());System.out.println(date4);System.out.println(date5);

4. Date 的时区确定

那 Date 是怎么确定当前时区的呢? 带着疑问看了下 toString 方法:

代码语言:javascript复制
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();}
private final BaseCalendar.Date normalize() {    if (cdate == null) {        BaseCalendar cal = getCalendarSystem(fastTime);        cdate = (BaseCalendar.Date) cal.getCalendarDate(fastTime,            TimeZone.getDefaultRef());        return cdate;    }    // Normalize cdate with the TimeZone in cdate first. This is    // required for the compatible behavior.    if (!cdate.isNormalized()) {        cdate = normalize(cdate);    }
    // If the default TimeZone has changed, then recalculate the    // fields with the new TimeZone.    TimeZone tz = TimeZone.getDefaultRef();    if (tz != cdate.getZone()) {        cdate.setZone(tz);        CalendarSystem cal = getCalendarSystem(cdate);        cal.getCalendarDate(fastTime, cdate);    }    return cdate;}
static TimeZone getDefaultRef() {    TimeZone defaultZone = defaultTimeZone;    if (defaultZone == null) {        // Need to initialize the default time zone.        defaultZone = setDefaultZone();        assert defaultZone != null;    }    // Don't clone here.    return defaultZone;}
private static synchronized TimeZone setDefaultZone() {    TimeZone tz;    // get the time zone ID from the system properties    String zoneID = AccessController.doPrivileged(        new GetPropertyAction("user.timezone"));
    // if the time zone ID is not set (yet), perform the    // platform to Java time zone ID mapping.    if (zoneID == null || zoneID.isEmpty()) {        String javaHome = AccessController.doPrivileged(            new GetPropertyAction("java.home"));        try {            zoneID = getSystemTimeZoneID(javaHome);            if (zoneID == null) {                zoneID = GMT_ID;            }        } catch (NullPointerException e) {            zoneID = GMT_ID;        }    }
    // Get the time zone for zoneID. But not fall back to    // "GMT" here.    tz = getTimeZone(zoneID, false);
    if (tz == null) {        // If the given zone ID is unknown in Java, try to        // get the GMT-offset-based time zone ID,        // a.k.a. custom time zone ID (e.g., "GMT-08:00").        String gmtOffsetID = getSystemGMTOffsetID();        if (gmtOffsetID != null) {            zoneID = gmtOffsetID;        }        tz = getTimeZone(zoneID, true);    }    assert tz != null;
    final String id = zoneID;    AccessController.doPrivileged(new PrivilegedAction < Void > () {        @Override        public Void run() {            System.setProperty("user.timezone", id);            return null;        }    });
    defaultTimeZone = tz;    return tz;}
private static native String getSystemTimeZoneID(String javaHome);

总结下:就是先根据系统变量 user.timezone 获取,若未设置最后调用到本地方法根据 javaHome 获取。

一般当前时区配置在 /etc/localtime 里, 多有的地区对应的时区库在 /var/db/timezone/zoneinfo。

转自:2021不再有雨, 链接:blog.csdn.net/w727655308/article/details/125211726

------

我们创建了一个高质量的技术交流群,与优秀的人在一起,自己也会优秀起来,赶紧点击加群,享受一起成长的快乐。另外,如果你最近想跳槽的话,年前我花了2周时间收集了一波大厂面经,节后准备跳槽的可以点击这里领取

推荐阅读

  • 12小时复刻《羊了个羊》,代码已开源!
  • 程序员想干一辈子行不行?
  • 微软全力拥抱 Java !

··································

你好,我是程序猿DD,10年开发老司机、阿里云MVP、腾讯云TVP、出过书创过业、国企4年互联网6年。从普通开发到架构师、再到合伙人。一路过来,给我最深的感受就是一定要不断学习并关注前沿。只要你能坚持下来,多思考、少抱怨、勤动手,就很容易实现弯道超车!所以,不要问我现在干什么是否来得及。如果你看好一个事情,一定是坚持了才能看到希望,而不是看到希望才去坚持。相信我,只要坚持下来,你一定比现在更好!如果你还没什么方向,可以先关注我,这里会经常分享一些前沿资讯,帮你积累弯道超车的资本。

点击领取2022最新10000T学习资料

0 人点赞