夏令时处理

夏令时介绍

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函数要考虑的情况:

  情况1:客户端为无夏令时的时区,比如:UTC+8
    a) 夏令时时间字符为: 2024-03-10 03:00:00 UTC-5
      客户端和服务器的时区偏差: 8 - (-5) = 13h
      在客户端会被当作: 2024-03-10 03:00:00 UTC+8, 非夏令时
      转为UTC+0时间后(-8h): 2024-03-09 19:00:00 UTC+0
      修正时区偏差后(+13h): 2024-03-10 08:00:00 UTC+0
      夏令时在客户端当做非夏令时处理了, 没扣夏令时多的那一个小时
      少扣的夏令时要扣掉(-1h): 2024-03-10 07:00:00 UTC+0

  情况2:客户端为有夏令时的时区,比如:UTC+2(雅典)
    a) 夏令时时间字符为: 2024-03-10 03:00:00 UTC-5
      客户端和服务器的时区偏差: 2 - (-5) = 7h
      在客户端会被当作: 2024-03-10 03:00:00 UTC+2, 非夏令时
      转为UTC+0时间后(-2h): 2024-03-10 01:00:00 UTC+0
      修正时区偏差后(+7h): 2024-03-10 08:00:00 UTC+0
      夏令时在客户端当做非夏令时处理了, 没扣夏令时多的那一个小时
      少扣的夏令时要扣掉(-1h): 2024-03-10 07:00:00 UTC+0

    b) 夏令时时间字符为: 2024-03-31 04:00:00 UTC-5
      客户端和服务器的时区偏差: 2 - (-5) = 7h
      在客户端会被当作: 2024-03-31 04:00:00 UTC+2, 夏令时
      转为UTC+0时间后(-2h): 2024-03-31 02:00:00 UTC+0
      修正时区偏差后(+7h): 2024-03-31 09:00:00 UTC+0

 

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) 要考虑的一些情况:

  情况1: 服务器为UTC+8时区(北京时间), 客户端也是UTC+8时区
    在同一个时区, 无需额外处理
 
  情况2: 服务器为UTC+8时区(北京时间), 客户端为UTC-5时区(美国东部时间,会有夏令时)
    客户端和服务器的时区偏差 = -5 - 8 = -13h
    a) 服务器时间: 2024-01-02 10:00:00 UTC+8, 非夏令时
      在客户端会被当作: 2024-01-02 10:00:00 UTC-5, 非夏令时
      转为UTC+0时间后(+5h): 2024-01-02 15:00:00 UTC+0
      修正时区偏差后(-13h): 2024-01-02 02:00:00 UTC+0
 
    b) 服务器时间: 2024-03-10 03:00:00 UTC+8, 非夏令时
      在客户端会被当作: 2024-03-10 03:00:00 UTC-5, 夏令时
      转为UTC+0时间后(+5h): 2024-03-10 07:00:00 UTC+0
      修正时区偏差后(-13h): 2024-03-09 18:00:00 UTC+0
      服务器非夏令时, 在客户端当夏令时处理了, 多扣了一个小时
      多扣的夏令时加回去(+1h): 2024-03-09 19:00:00 UTC+0
 
  情况3: 服务器为UTC-5时区(美国东部时间,会有夏令时), 客户端为UTC+8时区
    客户端和服务器的时区偏差 = 8 - (-5) = 13h
    a) 服务器时间: 2024-01-02 10:00:00 UTC-5, 非夏令时
      在客户端会被当作: 2024-01-02 10:00:00 UTC+8, 非夏令时
      转为UTC+0时间后(-8h): 2024-01-02 02:00:00 UTC+0
      修正时区偏差后(+13h): 2024-01-02 15:00:00 UTC+0
 
    b) 服务器时间: 2024-03-10 03:00:00 UTC-5, 夏令时
      在客户端会被当作: 2024-03-10 03:00:00 UTC+8, 非夏令时
      转为UTC+0时间后(-8h): 2024-03-09 19:00:00 UTC+0
      修正时区偏差后(+13h): 2024-03-10 08:00:00 UTC+0
      服务器夏令时, 在客户端当做非夏令时处理了, 没扣夏令时多的那一个小时
      少扣的夏令时要扣掉(-1h): 2024-03-10 07:00:00 UTC+0
 
  情况4: 服务器为UTC-5时区(美国东部时间,会有夏令时), 客户端为UTC+2时区(雅典时间,会有夏令时)
    客户端和服务器的时区偏差 = 2 - (-5) = 7h
    a) 服务器时间: 2024-01-02 10:00:00 UTC-5, 非夏令时
      在客户端会被当作: 2024-01-02 10:00:00 UTC+2, 非夏令时
      转为UTC+0时间后(-2h): 2024-01-02 08:00:00 UTC+0
      修正时区偏差后(+7h): 2024-01-02 15:00:00 UTC+0

    b) 服务器时间: 2024-03-10 03:00:00 UTC-5, 夏令时
      在客户端会被当作: 2024-03-10 03:00:00 UTC+2, 非夏令时
      转为UTC+0时间后(-2h): 2024-03-10 01:00:00 UTC+0
      修正时区偏差后(+7h): 2024-03-10 08:00:00 UTC+0
      服务器夏令时, 在客户端当做非夏令时处理了, 没扣夏令时多的那一个小时
      少扣的夏令时要扣掉(-1h): 2024-03-10 07:00:00 UTC+0
 
    c) 服务器时间: 2024-03-31 04:00:00 UTC-5, 夏令时
      在客户端会被当作: 2024-03-31 04:00:00 UTC+2, 夏令时
      转为UTC+0时间后(-2h和-1h): 2024-03-31 01:00:00 UTC+0
      服务器夏令时, 客户端也当夏令时处理了, 无需做夏令时修正
      修正时区偏差后(+7h): 2024-03-31 08:00:00 UTC+0
 
 

参考

欧美地区的夏令时有什么意义? - 知乎 (zhihu.com)

解析美国东部时间与北京时间相互转换的实现代码_C#教程_脚本之家 (jb51.net)

世界时区对照表 - 哔哩哔哩 (bilibili.com)

美国(太平洋时区)夏令时 - 碎豆芽 - 博客园 (cnblogs.com)

 

posted @   yanghui01  阅读(450)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示