为啥活动提前结束了?记 Date 类型的一次踩坑!
线上问题
新功能的营销活动时间 23:59:59 才结束, 但是 16 点多发现页面显示"活动已结束"。
排查过程
HTTP 抓数据包
activityEndTime 为 1654761599000, 转化为 Date 为 2022-06-09 15:59:59。
问题显而易见, 少了 8 个小时. 且不是前端的问题。
查询 DB 数据
活动结束时间是分为两个字段储存的:
endDate (dateTime类型): 2022-06-09
endTime(time类型): 23:59:59
查看缓存
endDate=1654704000000, endDate=57599000
换算了下没问题, 说明数据从 DB -> Java 中 Date 类型 -> JSON 序列化,链路是没问题的。
那只有一种可能:endDate 和 endTime 拼接成 activityEndTime 的时候出问题了。
查看拼接逻辑
activityEndTime = endDate.getTime() + endTime.getTime();
这太简单了, 难道会有问题?Debug 跟了一下, 果然结果有问题!
带着疑问点开了 Date.getTime() 方法:
/**
* 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();
}
January 1, 1970, 08:00:00
57599000 / 3600000 = 15:59:59
new Date(57599000L).toString()
Thu Jan 01 23:59:59 CST 1970
问题的原因
activityEndTime = endDate.getTime() + endTime.getTime()
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 方法:
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
推荐阅读
你好,我是程序猿DD,10年开发老司机、阿里云MVP、腾讯云TVP、出过书创过业、国企4年互联网6年。从普通开发到架构师、再到合伙人。一路过来,给我最深的感受就是一定要不断学习并关注前沿。只要你能坚持下来,多思考、少抱怨、勤动手,就很容易实现弯道超车!所以,不要问我现在干什么是否来得及。如果你看好一个事情,一定是坚持了才能看到希望,而不是看到希望才去坚持。相信我,只要坚持下来,你一定比现在更好!如果你还没什么方向,可以先关注我,这里会经常分享一些前沿资讯,帮你积累弯道超车的资本。