上一篇博客我们分析基于 APNs 实现 iOS Push Notification 的代码,讲解关键环节的关键代码。相信大部份同学看完上一篇就可以复制出自己的消息推送功能。这一篇则是结合我们产品的特殊需求,介绍时区处理,以便给不同时区的用户定时推送消息。有出海和全球化需求的同学应该会需要相关知识。
关于时区的基本知识
地球分为 24 个时区,从国际日期变更线开始(大概是夏威夷还是哪儿)为+12,即国际标准时间 0 点的时候,这块儿是中午 12 点。其实最多到 +16,不过都是太平洋小岛国,人口稀少,所以我们可以先不考虑那么细。向西逐渐减少,日韩+9,我们国家 +8,泰国越南+7。然后继续向西到英国格林尼治是 +0,即国际标准时 UTC。再往西到美国,再到太平洋上,一路最低好像是 -14。
这个是基础知识。我们国家比较简单,东到浙江西到新疆都是 +8,所以几乎不需要时区换算。但是有些国家比如澳大利亚就有 5 个时区,而且有 0.5 个时区的存在,所以算起来很麻烦。
所以,假设有一个中国用户,他希望北京时间早上 8 点的时候我们推送一条消息给他。那么经过换算,北京是 +8 区,所以就是 UTC 凌晨 0 点的时候我们要推送消息给他。我们推送消息使用 cronjob,而 cronjob 按照 UTC 时间来执行。那么综上所述,我们就要让 cronjob 执行的程序以 UTC 判断当前时间后,在 UTC 0 点推送一条消息给这位用户。
本地设置
我直接使用字符串保存用户本地设置的时间,比如 08:00 就是早上 8 点。接下来同步到服务器的时候,配合 Date.prototype.getTimezoneOffset()
获取用户当前的时区偏移值(按照分钟记),保存到数据库里。函数代码如下:
function toUtcMinute(time: string) {
const date = new Date();
const [ hour, minute ] = time.split(':').map(Number);
const utcMinute = hour * 60 + minute + date.getTimezoneOffset();
return utcMinute < 0 ? 86400 + utcMinute : utcMinute;
}
这样一来,+8 区 08:00 保存的结果就是 0,方便服务器上 cronjob 从数据库里取数据。
同时我还要保存 getTimezoneOffset()
的值到服务器,因为展示给用户看的时间应该是本地化的时间,而不是 UTC 时间。
服务器脚本
我的 cronjob 每 5 分钟跑一次,取出 5 分钟内需要推送的消息发给所有设定了自动推送的用户。因为我们目前的用户数量很少,而且要推送的消息时效性也不强,这样做基本足够。如果将来用户数量多了,或者我们对时效性要求提高,我会升级推送端的设计,到时候可能再出一篇文章。目前这样足够:
const time = new Date();
const minute = time.getUTCHours() * 60 + time.getUTCMinutes();
const { data: notifications, error } = await supabase.from(TABLE_REMINDERS)
.select('*')
.gte('time', minute)
.lt('time', minute + 5)
.order('time');
for (const notification of notifications as NotificationItem[]) {
const time = dayjs();
// 因为服务器端 cronjob 里统一是 UTC,所以这里直接修改时间对象
time.subtract(notification.timezone, 'minutes');
const timeStr = time.format('H:ma'); // 格式化的结果是用户的本地时间
const dateStr = time.format('MMMM D'); // 同上
// 其它推送操作
}
总结
我国时区统一,不需要转换时区,给我们节省了大量的时间。但是国际上可能同一个国家不同时区,或者不同国家地理位置接近但是使用不同时区都很常见。所以我们必须掌握时区转换的基本知识,以应对出海的需求。
希望这系列文章对大家有帮助。如果你对移动应用开发、消息推送、出海等感兴趣,欢迎留言讨论。如果需要其它领域的知识,也欢迎主动点菜。
本站目前仍在招商中,感兴趣的老板请与我联系。
【系列教程】使用 Vercel Serverless function 连接 APNs 实现 iOS 推送通知
欢迎吐槽,共同进步