夏令时处理
夏令时介绍
a) 夏令时是什么?
说简单点就是人为调快时间1个小时。
b) 为什么要调快1个小时?
因为西方的一些国家在3-10月份的时候,早上日出时间很早,到7,8点上班时间的时候,太阳可能都达到正午的那种太阳了,然后下午又3,4点就日落了;
这不符合日出而作,日落而息这样的规律,于是就用了夏令时这个解决办法,进入3-10月份,就人为的把时间调快1个小时。
c) 美国夏令时
美国夏令时一般在3月第二个周日凌晨2AM(当地时间)开始,将时钟调到3点,拨快1小时,俗称“Spring Forward 1 Hour”;而在11月第一个周日凌晨2AM(当地时间)夏令时结束,要将时钟调到1点,拨慢1小时,俗称“Fall Back 1 Hour”。
2024年夏令时从03-10 02:00(03:00 dst)开始,到11-03 02:00 dst结束。
夏令时开始时,2024-03-10 01:59:59下一秒就直接变为2024-03-10 03:00:00
夏令时结束时,2024-11-03 01:59:59下一秒变为2024-11-03 01:00:00
d) 欧洲雅典夏令时
夏令时在每年三月的最后一个星期天,凌晨3点调整时钟快1小时,结束于十月的最后一个星期天,凌晨3点调整时钟回正常时间。
2024年夏令时从03-31 03:00(04:00 dst)开始(02:59:59下一秒变为04:00:00),到11-03 04:00 dst结束(03:59:59下一秒变为03:00:00)。
代码部分
1) 获取时区
local Mgr = {} TimeZoneMgr = Mgr ---获取客户端时区(用秒表示) function Mgr.GetLocalTimeZoneSec() local nowUtcSec = os.time() local dt = os.date("!*t", nowUtcSec) local timeZoneSec = os.difftime(nowUtcSec, os.time(dt)) return timeZoneSec end
2) 时区,夏令时初始化
---服务器所在时区 Mgr.m_TimeZoneSec = 0 ---服务器夏令时开始时间 Mgr.m_DstBeginTimeInfo = nil ---服务器夏令时结束时间 Mgr.m_DstEndTimeInfo = nil ---服务器夏令时开始时间(UTC+0时间表示) Mgr.m_DstBeginUtcSec = 0 ---服务器夏令时结束时间(UTC+0时间表示) Mgr.m_DstEndUtcSec = 0 ---夏令时时间字符串转为UTC+0时间戳 ---@param dstTimeFormat string 夏令时时间, 格式: yyyy-MM-dd HH:mm:ss function Mgr.ConvertDstTimeFormatToTimestamp(dstTimeFormat) local pattern = "(%d+)-(%d+)-(%d+) (%d+):(%d+):(%d+)" local index1, index2, yearStr, monthStr, dayStr, hourStr, minStr, secStr = string.find(dstTimeFormat, pattern) local date = { year = tonumber(yearStr), month = tonumber(monthStr), day = tonumber(dayStr), hour = tonumber(hourStr), min = tonumber(minStr), sec = tonumber(secStr), } local utcSec = os.time(date) --按本机时区转UTC+0时间 --print(os.date("!%c", utcSec)) local clientIsdst = os.date("*t", utcSec).isdst --date对应的时间在客户端是否为夏令时 local timeZoneDelta = Mgr.GetLocalTimeZoneSec() - Mgr.m_TimeZoneSec utcSec = utcSec + timeZoneDelta --时区偏差修正 if not clientIsdst then utcSec = utcSec - 3600 --夏令时在本地当做非夏令时处理了, 少扣的1小时扣掉 end --print(os.date("!%c", utcSec)) return utcSec end ---@param timeZone number 时区(用小时表示) ---@param dstBeginTimeFormat string|null 夏令时开始时间, 格式: yyyy-MM-dd HH:mm:ss ---@param dstEndTimeFormat string|null 夏令时结束时间, 格式: yyyy-MM-dd HH:mm:ss function Mgr.SetTimeZoneAndDst(timeZone, dstBeginTimeFormat, dstEndTimeFormat) Mgr.m_TimeZoneSec = timeZone * 3600 if dstBeginTimeFormat and dstEndTimeFormat then local pattern = "(%d+)-(%d+)-(%d+) (%d+):(%d+):(%d+)" local index1, index2, yearStr, monthStr, dayStr, hourStr, minStr, secStr = string.find(dstBeginTimeFormat, pattern) Mgr.m_DstBeginTimeInfo = { tonumber(yearStr), tonumber(monthStr), tonumber(dayStr), tonumber(hourStr), tonumber(minStr), tonumber(secStr) } index1, index2, yearStr, monthStr, dayStr, hourStr, minStr, secStr = string.find(dstEndTimeFormat, pattern) Mgr.m_DstEndTimeInfo = { tonumber(yearStr), tonumber(monthStr), tonumber(dayStr), tonumber(hourStr), tonumber(minStr), tonumber(secStr) } Mgr.m_DstBeginUtcSec = Mgr.ConvertDstTimeFormatToTimestamp(dstBeginTimeFormat) Mgr.m_DstEndUtcSec = Mgr.ConvertDstTimeFormatToTimestamp(dstEndTimeFormat) else Mgr.m_DstBeginTimeInfo = nil Mgr.m_DstEndTimeInfo = nil Mgr.m_DstBeginUtcSec = 0 Mgr.m_DstEndUtcSec = 0 end end
2-a) Mgr.ConvertDstTimeFormatToTimestamp函数要考虑的情况:
3) 夏令时判断
function Mgr.TimeInfoIsdst(timeInfo) if nil == Mgr.m_DstBeginTimeInfo or nil == Mgr.m_DstEndTimeInfo then return false end local isLtDstBegin, isGtDstBegin = false, false local isLtDstEnd, isGtDstEnd = false, false for i, v in ipairs(timeInfo) do if not isLtDstBegin and not isGtDstEnd then local timeUnit = Mgr.m_DstBeginTimeInfo[i] if v < timeUnit then isLtDstBegin = true elseif v > timeUnit then isGtDstBegin = true end end if not isLtDstEnd and not isGtDstEnd then local timeUnit = Mgr.m_DstEndTimeInfo[i] if v > timeUnit then isGtDstEnd = true elseif v < timeUnit then isLtDstEnd = true end end if isGtDstBegin and isLtDstEnd then return true end if isLtDstBegin or isGtDstEnd then return false end end -- =dstBegin or =dstEnd return true end ---服务器时间字符串是否为夏令时 ---@param timeFormat string 要检查的服务器时间字符串, 格式: yyyy-MM-dd HH:mm:ss function Mgr.TimeFormatIsdst(timeFormat) if Mgr.m_DstBeginTimeInfo and Mgr.m_DstEndTimeInfo then local pattern = "(%d+)-(%d+)-(%d+) (%d+):(%d+):(%d+)" local index1, index2, yearStr, monthStr, dayStr, hourStr, minStr, secStr = string.find(timeFormat, pattern) local timeInfo = { tonumber(yearStr), tonumber(monthStr), tonumber(dayStr), tonumber(hourStr), tonumber(minStr), tonumber(secStr) } return Mgr.TimeInfoIsdst(timeInfo) end return false end ---@param utcSec number UTC+0时间 function Mgr.TimestampIsdst(utcSec) if Mgr.m_DstBeginTimeInfo and Mgr.m_DstEndTimeInfo then return utcSec >= Mgr.m_DstBeginUtcSec and utcSec < Mgr.m_DstEndUtcSec end return false end
4) 时间转换
---@param timeFormat string 服务器时间字符串, 格式: yyyy-MM-dd HH:mm:ss function Mgr.TimeFormatToTimestamp(timeFormat) local pattern = "(%d+)-(%d+)-(%d+) (%d+):(%d+):(%d+)" local index1, index2, yearStr, monthStr, dayStr, hourStr, minStr, secStr = string.find(timeFormat, pattern) local timeInfo = { tonumber(yearStr), tonumber(monthStr), tonumber(dayStr), tonumber(hourStr), tonumber(minStr), tonumber(secStr) } local serverIsdst = Mgr.TimeInfoIsdst(timeInfo) local date = { year = timeInfo[1], month = timeInfo[2], day = timeInfo[3], hour = timeInfo[4], min = timeInfo[5], sec = timeInfo[6] } local utcSec = os.time(date) print(os.date("!%c", utcSec)) local clientIsdst = os.date("*t", utcSec).isdst --date对应的时间在客户端是否为夏令时 local timeZoneDelta = Mgr.GetLocalTimeZoneSec() - Mgr.m_TimeZoneSec utcSec = utcSec + timeZoneDelta --时区偏差修正 if serverIsdst then if not clientIsdst then utcSec = utcSec - 3600 --夏令时修正: timeFormat是夏令时, 在客户端当非夏令时处理了, 少扣的1个小时扣掉 end elseif clientIsdst then utcSec = utcSec + 3600 --夏令时修正: timeFormat不是夏令时, 在客户端当夏令时处理了, 多扣的1个小时加回去 endreturn utcSec end
function Mgr.TimestampToTimeFormat(utcSec) local serverIsdst = Mgr.TimestampIsdst(utcSec) utcSec = utcSec + Mgr.m_TimeZoneSec if serverIsdst then utcSec = utcSec + 3600 end local date = os.date("!*t", utcSec) --手动处理时区,isdst总是为falsereturn date end
4-a) 要考虑的一些情况:
参考
欧美地区的夏令时有什么意义? - 知乎 (zhihu.com)
解析美国东部时间与北京时间相互转换的实现代码_C#教程_脚本之家 (jb51.net)
美国(太平洋时区)夏令时 - 碎豆芽 - 博客园 (cnblogs.com)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?