CocosCreator - JavaScript内有关判断日期的需求
基本需求
在游戏外围系统的开发过程中,经常会遇到倒计时等功能的实现。就需要掌握一些基本操作并灵活使用JS Date类型的技巧。
Date类型常用使用技巧
- 创建当前的日期的对象
new Date()
- 创建自定义日期
// 下面这几种写法都是一样的,表示同样的北京时间15号早上6点55这个时间点:
new Date('Mon Jul 15 2019 06:55:40.160 GMT+0800')
new Date('Mon Jul 15 2019 06:55:40:160 GMT+0800')
new Date('2019-07-14T22:55:40.160Z')
new Date('2019-07-15T06:55:40.160+0800')
// log 出来的结果都是 2019-07-14T22:55:40.160Z
其中后面的Z表示 UTC时间,因此UTC时间还是14号。
而GMT,或者直接写+0800表示当前时区。因此,date类型内实际上可以表示全球唯一的时间点,时区只是显示形式,或者一些计算函数的依赖。
- 获取星期几
// 0为周日 其它返回整数几就是星期几
d1.getDay()
- 计算加减一个时间后的量,比如很多游戏中要算出一个时间之后多少秒是哪个时刻。
let d1 = new Date('2019-07-15T06:55:40.160+0800')
let d2 = d1.setSeconds(d1.getSeconds()+30);
let d3 = new Date(d2);
// 此时d1,d2,d3都是从原始d1往后推30秒之后的时间,但其中d1和d3是Date对象,d2是Date对象的原始值(整数)
// 而且注意到方便之处在于我们不用去维护进位了。
// 原始值是指 1970-01-01:00:00:00:000Z 到该时间点的毫秒数
// 其中还可以使用setUTCXxx和getUTCXxx的,只要配套使用就好了
- 即时两个日期完全相同,但直接无法判断相等。但可以相减判断时间差与零的关系。
let d1 = new Date('2019-07-15T06:55:40.160+0800');
let d2 = new Date('2019-07-15T06:55:40.160+0800');
console.log(d1==d2) // false
console.log(d1-d2==0) // true
// 若d1-d2>0 则d1的时间点在d2之后,反之同理
- 判断两个时间点在日期上为同一天(先忽略时区问题),比如每日奖励,每天就1次,0点整刷新
let d1 = new Date('2019-07-15T06:55:40.160Z');
let d2 = new Date('2019-07-15T19:55:40.160Z');
let d1t = d1.setUTCHours(0,0,0,0);
let d2t = d2.setUTCHours(0,0,0,0);
console.log(d1t, d2t, d1t == d2t);
// 思路是使用setUTCHours约去了日期之后的信息。那么日期相等就是同一天了。
- 在上一个问题中,一般来说全球化的游戏需要以每个玩家地区的各自的零点来刷新。比如带入这两个日期:
let d1 = new Date('2019-07-15T07:55:40+0800');
let d2 = new Date('2019-07-15T08:05:40+0800');
let d1t = d1.setUTCHours(0,0,0,0);
let d2t = d2.setUTCHours(0,0,0,0);
console.log(d1t, d2t, d1t == d2t);
// 竟然false了。北京时间点7:55和8:05竟然不是同一天!
改正也很简单:把上面两个setUTCHours改为setHours即可
let d1 = new Date('2019-07-15T07:55:40+0800');
let d2 = new Date('2019-07-15T08:05:40+0800');
let d1t = d1.setHours(0,0,0,0);
let d2t = d2.setHours(0,0,0,0);
console.log(d1t, d2t, d1t == d2t);
// 这次为true。仔细研究发现其中的d1t和d2t这两个原始值均表示时刻2019-07-14T16:00:00.000Z
- 上个需求再加点内容:要显示出来距离下一个刷新的点还需要多久?(下一个时间点就是明天0点)
let d1 = new Date('2019-07-15T07:55:40+0800'); // 输入上次领取的奖励时间记录
let d2 = new Date(); // 获取当前时间
d1.setHours(24,0,0,0); // d1此时已经表示下次刷新点, 如果是第二天0点的话这样写就足够了,还很简单。
// *如果没有理解上面的setHours(0,0,0,0),理解这里就会有障碍。如果仍然写为 d1.setHours(0,0,0,0),那么紧接着应该再写 d1.setDate(d1.getDate()+1); 写这两句是一样的。
// *还需要注意setHours后面写4个参数小时,时,分,秒没问题。而setDate后面仅仅只能跟年月日的日这一个参数。
let diff = d1 - d2;
// d1-d2>0 是指离下次刷新还有的毫秒数,反之是已经超过了刷新时间多少毫秒
- 有关显示倒计时,游戏中经常的需求是超过了1天,仍然把天换算成24小时加在小时数字上面,或者说超过了1个月,也写做多少天,又或者说超过1天了,忽略显示时分秒等等。那么我经常使用到我封装好的一个函数。把时间差原始值带入即可。
let msResolve = function (tms) {
let sign = Math.sign(tms);
let ms = tms % 1000;
let ts = Math.floor(tms / 1000);
let s = ts % 60;
let tm = Math.floor(ts / 60);
let m = tm % 60;
let th = Math.floor(tm / 60);
let h = th % 24;
let td = Math.floor(th / 24);
return {
sign: sign,
td: td,
th: th,
tm: tm,
ts: ts,
tms: tms,
h: h,
m: m,
s: s,
ms: ms
}
};
// 上一个需求的时间差带入,返回值则为
/* { sign: 1,
td: 0,
th: 12,
tm: 764,
ts: 45847,
tms: 45847961,
h: 12,
m: 44,
s: 7,
ms: 961 } */
//d h m s ms 表示 天 时 分 秒 毫秒,
//t表示对应的总量
// 这样在显示时就很方便的进行各种显示了
项目中用到的几种时间表示法:
// xD hh:mm:ss 小于1D时 xD隐藏
DateTools.solvDisplay1 = function (solv) {
return "" + (solv.td > 0 ? solv.td + 'D ' : '') + solv.h.toString().fill0(2) + ":" + solv.m.toString().fill0(2) + ":" + solv.s.toString().fill0(2);
};
// 大于1天时使用xD ,否则使用hh:mm:ss
DateTools.solvDisplay2 = function (solv) {
return solv.td > 0 ? solv.td + 'D' : DateTools.solvDisplay1(solv);
};
// 大于1天 dd:hh:mm 否则 hh:mm:ss
DateTools.solvDisplay3 = function (solv, dayStr) {
if (solv.td > 0) {
return '{}{} {}:{}:{}'.format(solv.td, dayStr, solv.h.toString().fill0(2), solv.m.toString().fill0(2), solv.s.toString().fill0(2));
} else {
return solv.h.toString().fill0(2) + ":" + solv.m.toString().fill0(2) + ":" + solv.s.toString().fill0(2);
}
};
// xD hh:mm:ss 小于1D时 xD隐藏
DateTools.solvDisplay4 = function (solv) {
return solv.h.toString().fill0(2) + ":" + solv.m.toString().fill0(2) + ":" + solv.s.toString().fill0(2);
};
// *fill0(2)是为了补0到2位数
- 然而在全球服的服务器代码中,问题又来了。在非本时区的服务器上,setHours经常都是以服务器端setHours为准,有的云服务,比如LeanCloud的云服务。都是以UTC时间为准。那么,相当于服务器端只能做setUTCXxx。(其实是set/getXxx 和set/getUTCXxx都一样作用),如果要服务器验证玩家领取的每日奖励是否为同一天呢?
比如下面:
let d1 = new Date('2019-07-15T23:55:40+0800');
let d2 = new Date('2019-07-16T00:00:40+0800');
let d3 = new Date('2019-07-15T07:55:40+0800');
let d4 = new Date('2019-07-15T08:05:40+0800');
let d1t = d1.setUTCHours(0,0,0,0);
let d2t = d2.setUTCHours(0,0,0,0);
let d3t = d3.setUTCHours(0,0,0,0);
let d4t = d4.setUTCHours(0,0,0,0);
console.log('--------------')
console.log(d1, d1.toGMTString());
console.log(d2, d2.toGMTString());
console.log(d1t, d2t, d1t==d2t);
console.log('--------------')
console.log(d3, d3.toGMTString());
console.log(d4, d4.toGMTString());
console.log(d3t, d4t, d3t==d4t);
// 会发现 d1 d2 为同一天,而 d3 d4 不为同一天
这里面的原因就是服务器并不知道玩家所在时区,也就不知道针对每个时区每天刷新点。
改进:
let d1 = new Date('2019-07-15T23:55:40+0800');
let d2 = new Date('2019-07-16T00:00:40+0800');
let d3 = new Date('2019-07-15T07:55:40+0800');
let d4 = new Date('2019-07-15T08:05:40+0800');
let timezoneOffset = new Date().getTimezoneOffset(); // 从客户端传入,意味着客户端告诉服务器,我来自地球的哪个时区
// 先假设回0时区,过滤后可以只当输入的时间就是0时区玩家的时间,而玩家就是0时区玩家。(这里就是算法核心)
d1.setUTCMinutes(d1.getUTCMinutes() - timezoneOffset);
d2.setUTCMinutes(d2.getUTCMinutes() - timezoneOffset);
d3.setUTCMinutes(d3.getUTCMinutes() - timezoneOffset);
d4.setUTCMinutes(d4.getUTCMinutes() - timezoneOffset);
// 在按0时区的一天进行判断
let d1t = d1.setUTCHours(0,0,0,0);
let d2t = d2.setUTCHours(0,0,0,0);
let d3t = d3.setUTCHours(0,0,0,0);
let d4t = d4.setUTCHours(0,0,0,0);
// 输出结果
console.log('-------timezoneOffset------', timezoneOffset)
console.log('--------------')
console.log(d1, d1.toGMTString());
console.log(d2, d2.toGMTString());
console.log(d1t, d2t, d1t==d2t);
console.log('--------------')
console.log(d3, d3.toGMTString());
console.log(d4, d4.toGMTString());
console.log(d3t, d4t, d3t==d4t);
/*
输出:
-------timezoneOffset------ -480
--------------
2019-07-15T00:00:00.000Z 'Mon, 15 Jul 2019 00:00:00 GMT'
2019-07-16T00:00:00.000Z 'Tue, 16 Jul 2019 00:00:00 GMT'
1563148800000 1563235200000 false
--------------
2019-07-15T00:00:00.000Z 'Mon, 15 Jul 2019 00:00:00 GMT'
2019-07-15T00:00:00.000Z 'Mon, 15 Jul 2019 00:00:00 GMT'
1563148800000 1563148800000 true
*/
// *如果服务器时区不是0时区或者不采用UTC时间的话,还需要先偏移一个服务器时区。