微信消息显示时间研究 | 深先院羽毛球预约启示
背景
深先院的体育设施一直十分紧缺,羽毛球场经过假期改造后,终于重见天日。球场数量惊人地增长了50%,由原来2个场地增加到了如今3个场地! o.o
去年以前预约羽毛球需要在微信小程序/网站上选择时间段,据说为了避免脚本外挂,目前改为了微信消息预定。最近潜入到“SIAT羽毛球爱好者”微信群聊才熟悉预约规则。2022-4-16编辑的群公告介绍如下:
订场须知,每天早上八点开始订场,加当天只能预约3天内的场地,
工作日每个人只能预约1小时,节假日可以预约2小时,
如果节假日碰上周一至周五的18:00—20:00是不能预约,
如果有人违规订场,我会把场地直接取消
由于场地数量有限,羽毛球爱好者的主要矛盾是人民日益增长的打球锻炼需要和不平衡不充分的场地之间的矛盾。预约有多火爆呢?每日老师收到的微信预定消息多达90+。
为了保证能优先抢到球场,必须得在发送时间上尽可能靠前(且不能早于8点)。这有必要弄清楚微信消息显示时间的机制。
术语
时间戳:自1970年1月1日0时0分0秒到当前时刻经过的秒数。计算机一般用时间戳记录时间,为浮点数。
协调世界时(UTC):目前国际通用的计时标准,取代了格林威治时间(GMT时间)。不同国家和地区采用的本地时间 = UTC标准时间+偏移量,如北京时间为 UTC+8(小时)。
时间格式:YYYY-MM-DDThh:mm:ss,UTC时间显示方式,如2022-11-16T12:00:00Z表示为北京时间2022年11月16日20点0分0秒。
微信发送方显示时间:即实际消息成功发送时间,未成功发出时,会提示重发,发送成功后更新为发出时间。
微信接收方显示时间:即消息抵达微信服务器的时间。很容易验证,接收方显示时间与发送方可以不一致,也与接收方在线/不在线/网速无关,因此推测该显示时间为消息实际到达服务器时间。
注:
1)微信显示时间为向下取整,即 08:00:00 表示 08:00:00.0 ~ 08:00:00.99...之间所有时刻。
2)Excel 对于时间戳转换时间格式显示,会四舍五入,即 08:00:00 表示 07:59:59.50 ~ 08:00:00.499...之间所有时刻。
实验
1)测量脚本执行时间
重复调用自动点击脚本,取多次运行平均时间,得到脚本执行时间为 0.56s
2)测量微信发送时延
每隔一分钟增加 0.01s sleep时间,发送微信消息,记录接收方显示时间。
结果记录如下:
循环进入时间戳(s,世界时) | 循环进入分钟 | 阻塞时间 | 程序触发理论时间 | 程序触发理论分钟 | 微信理论发送时间 | 微信显示时间 | 程序全链路总延时(s) |
---|---|---|---|---|---|---|---|
1667538309.575 | 13:05:09 | 0 | 1667538309.575 | 13:05:10 | 1667538310.135 | 13:05:10 | 0 |
1667538360.010 | 13:06:00 | 0.01 | 1667538360.020 | 13:06:00 | 1667538360.580 | 13:06:01 | 1 |
1667538420.001 | 13:07:00 | 0.02 | 1667538420.021 | 13:07:00 | 1667538420.581 | 13:07:01 | 1 |
1667538480.006 | 13:08:00 | 0.03 | 1667538480.036 | 13:08:00 | 1667538480.596 | 13:08:01 | 1 |
1667538540.006 | 13:09:00 | 0.04 | 1667538540.046 | 13:09:00 | 1667538540.606 | 13:09:01 | 1 |
. | . | . | . | . | . | . | . |
1667542320.014 | 14:12:00 | 0.67 | 1667542320.684 | 14:12:01 | 1667542321.244 | 14:12:01 | 1 |
1667542380.007 | 14:13:00 | 0.68 | 1667542380.687 | 14:13:01 | 1667542381.247 | 14:13:01 | 1 |
1667542440.008 | 14:14:00 | 0.69 | 1667542440.698 | 14:14:01 | 1667542441.258 | 14:14:01 | 1 |
1667542500.003 | 14:15:00 | 0.7 | 1667542500.703 | 14:15:01 | 1667542501.263 | 14:15:02 | 2 |
1667542560.015 | 14:16:00 | 0.71 | 1667542560.725 | 14:16:01 | 1667542561.285 | 14:16:02 | 2 |
1667542620.006 | 14:17:00 | 0.72 | 1667542620.726 | 14:17:01 | 1667542621.286 | 14:17:02 | 2 |
. | . | . | . | . | . | . | . |
1667548140.011 | 15:49:00 | 1.64 | 1667548141.651 | 15:49:02 | 1667548142.211 | 15:49:02 | 2 |
1667548200.014 | 15:50:00 | 1.65 | 1667548201.664 | 15:50:02 | 1667548202.224 | 15:50:02 | 2 |
1667548260.006 | 15:51:00 | 1.66 | 1667548261.666 | 15:51:02 | 1667548262.226 | 15:51:02 | 2 |
1667548320.014 | 15:52:00 | 1.67 | 1667548321.684 | 15:52:02 | 1667548322.244 | 15:52:03 | 3 |
1667548380.008 | 15:53:00 | 1.68 | 1667548381.688 | 15:53:02 | 1667548382.248 | 15:53:03 | 3 |
1667548440.013 | 15:54:00 | 1.69 | 1667548441.703 | 15:54:02 | 1667548442.263 | 15:54:03 | 3 |
1667548500.002 | 15:55:00 | 1.7 | 1667548501.702 | 15:55:02 | 1667548502.262 | 15:55:03 | 3 |
3)确定发送时间
注意到两处分钟跳变,灵敏度为0.01s。设程序执行时间为 t1=0.56±0.02s,发送时延为 t2。
根据第一处,0.69+t1+t2<2, 0.7+t1+t2>2,解得 0.72<t2<0.77
根据第二处,1.66+t1+t2<3, 1.67+t1+t2>3,解得 0.75<t2<0.80
这两部分结果差异可能与14:14和15:51网络环境和t1不同有关。可以估计发送时延t2为 0.76s 左右。
在具体设计发送时间时,可以直接使用上述相同的代码,提前两秒发送时,选择sleep 0.7s;或者提前三秒发送,选择sleep 1.67s。
4)新的脚本
在测试时延时,需要用到键盘操作复制黏贴,再用鼠标点击发送。实际预约时,只需要编辑好文字,鼠标点击即可。因此手动调整好跳变的时延参数。
本机测试的最佳sleep时间 0.77s,脚本执行时间+抵达微信服务器时间为 0.23s左右,可忽略发送时延在其中的影响。
核心在于,在07:59:59.77左右启动脚本,08:00.01秒左右发出并抵达微信服务器。
点击查看代码
import time
import sys
import *** # 自行百度引入相关库
def Fuck():
pass
# 自行实现预约逻辑
def getLastSecondTime(time_now):
hour, minu, sec = map(int, time_now.split(":"))
sec -= 1
if sec<0:
sec += 60
minu -= 1
if minu<0:
minu +=60
hour -= 1
if hour<0:
hour += 24
return "%02d:%02d:%02d" % (hour, minu, sec)
# 未指定参数
TIME_TO_FUCK = "07:59:59"
TIME_SLEEP = 0.77
if len(sys.argv)==2:
if sys.argv[1]=="test":
Fuck()
exit(0)
else:
try:
TIME_TO_FUCK = getLastSecondTime(sys.argv[1])
TIME_SLEEP = 0.77
except Exception as e:
print(e)
elif len(sys.argv)==3:
try:
TIME_TO_FUCK = getLastSecondTime(sys.argv[1])
TIME_SLEEP = float(sys.argv[2])
except Exception as e:
print(e)
# example
# python xxx.py test
# python xxx.py 8:0:0
# python xxx.py 8:0:0 0.88
print(TIME_TO_FUCK)
print(TIME_SLEEP)
# Fuck()
# exit(0)
# 一直阻塞等到8点
while True:
# 刷新时间
time_now = time.strftime("%H:%M:%S", time.localtime())
# 设置要执行的时间
if time_now == TIME_TO_FUCK:
# 要执行的代码
# print("要开始抢了!")
time.sleep(TIME_SLEEP)
Fuck()
exit(0)
else:
print(time.time(), time_now)
time.sleep(0.01)
最近几次预约记录的时间如下:
预约日期 | 星期 | sleep前的时间戳 | sleep 时间 |
执行完记录时间戳 | 排名 | 备注 |
---|---|---|---|---|---|---|
2022-11-4 | 星期五 | 未记录,在整点发送 08:00:00 |
0.0 | 未记录 | 18 | 成功,周末排前20可约上, 有人放弃早上场地,顺延 |
2022-11-7 | 星期一 | 1667779198.9975529 07:59:58 |
0.51 | 1667779200.0686145 | 17 | 失败,时间未同步, 基于前晚测试0.48临界值 |
2022-11-9 | 星期三 | 1667951998.9872577 07:59:58 |
0.76 | 1667952000.0060308 | -4 | 失败 |
2022-11-13 | 星期日 | 1668297598.9947047 07:59:58 |
0.77 | 1668297600.0295887 | 2 | 成功 |
2022-11-14 | 星期一 | 1668383998.9880173 07:59:58 |
0.77 | 1668384000.0224617 | 1/42 | 成功 |
2022-11-17 | 星期四 | 1668643198.9920938 07:59:58 |
0.9 | 1668643200.1645417 | 14/71 | 成功,有意调大 |
2022-11-21 | 星期一 | 1668988798.9841523 07:59:58 |
1.0 | 1668988800.267595 | 17/77 | 失败,有意调大 |
2022-11-22 | 星期二 | 1669075198.991925 07:59:58 |
0.78 | 1669075200.0428288 | 16/56 | 失败,翻车 |
2022-11-23 | 星期三 | 1669161598.9950874 07:59:58 |
0.77 | 1669161600.043384 | 17/47 | 失败,翻车 需要再调快0.02s |
2022-11-24 | 星期四 | 1669247998.9947767 07:59:58 |
0.75 | 1669248000.0114515 | 9/56 | 成功 |
2022-11-25 | 星期五 | 未保存记录 07:59:58 |
0.75 | 未保存,大约为...0.006 | 19/54 | 成功,未分配到预约时间, 可能时间未校准 |
2022-11-26 | 星期六 | 1669420798.9937348 07:59:58 |
0.75 | 1669420800.0087657 | 1/17 | 成功 |
2022-11-29 | 星期二 | 1669679998.9920964 07:59:58 |
0.75 | 1669680000.008575 | 19/35 | 失败,时间未校准 相比tsa.cn延后0.4s |
由上表第1行和第7行记录可见,在整点执行脚本排名会靠后,只有在周末才有机会约上球场,而周内只有排前十才有机会。
结论
想要预约排名靠前,成功约上球场,一定须知的两个注意事项:
1)同步本地时间与标准时间(电脑时钟每天误差可能有1~2秒)
2)请务必在08:00:00前发送(使得消息抵达服务器时间是真正的8点)
(完)