lua中处理时区和夏令时
一些工具函数
获取本地时区偏差
---@return integer 当前时区与UTC时间的偏移秒数 function TimeUtil.GetLocalTimeZoneOffset() local timestamp = os.time() local date = os.date("!*t", timestamp) local tzOffset = os.difftime(timestamp, os.time(date)) return tzOffset end
---用0时区时间字符串打印 function TimeUtilT.PrintTimestampUTC0(timestamp) local date = os.date("!*t", timestamp) local timeStr = string.format("%04d-%02d-%02d %02d:%02d:%02d UTC+0", date.year, date.month, date.day, date.hour, date.min, date.sec) print(timeStr) end
解析时间字符串
---@class DateParsed ---@field year integer ---@field month integer ---@field day integer ---@field hour integer ---@field min integer ---@field sec integer ---@field tzOffset integer|nil 时区偏移, 以秒为单位, 例如: -5*3600表示UTC-5, +8*3600表示UTC+8, nil表示没有时区信息 ---@param timeStr string ISO8601格式的时间字符串, 例如: "2024-10-03T01:00:00-05:00", "2024-10-03 01:00:00-05:00", "2024-10-03 01:00:00" ---@return DateParsed date function TimeUtil.TimeStrToDate(timeStr) local pattern = "^(%d+)-(%d+)-(%d+)[T ](%d+):(%d+):(%d+)([%+%-]?)(%d?%d?):?(%d?%d?)$" local index1, index2, year, month, day, h, m, s, tzSign, tzHour, tzMin = string.find(timeStr, pattern) local date = { year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0, tzOffset = 0 } date.year = tonumber(year) date.month = tonumber(month) date.day = tonumber(day) date.hour = tonumber(h) date.min = tonumber(m) date.sec = tonumber(s) local tzOffset = nil if tzHour and "" ~= tzHour then tzOffset = tonumber(tzHour) * 3600 end if tzMin and "" ~= tzMin then if nil == tzOffset then tzOffset = 0 end tzOffset = tzOffset + tonumber(tzMin) * 60 end if tzOffset and "-" == tzSign then tzOffset = -tzOffset end date.tzOffset = tzOffset return date end
TimeZone类
local Internal = {} function TimeZone:ctor() self.m_TimeZoneOffset = 0 self.m_LocalTimeZoneOffset = 0 self.m_DstBeginTimestamp = 0 self.m_DstEndTimestamp = 0 end ---@param self TimeZone function Internal.DstTimeStrToTimestamp(self, timeStr) local date = TimeUtil.TimeStrToDate(timeStr) local timestamp = os.time(date) --内部会减掉本地时区偏差, 比如: UTC+8会减掉 8*3600 timestamp = timestamp + self.m_LocalTimeZoneOffset --因为timeStr不是本地时区的时间, 把上面减掉的加回来 --像os.time做的那样, 减掉timeStr所在时区的偏差 local tzOffset = date.tzOffset if tzOffset then timestamp = timestamp - tzOffset -- 比如: -04:00就是-(-4*3600) else timestamp = timestamp - self.m_TimeZoneOffset -- 比如: UTC-5就是-(-5*3600) end return timestamp end function TimeZone:SetDstByTimeStr(dstBeginTimeStr, dstEndTimeStr) self.m_DstBeginTimestamp = Internal.DstTimeStrToTimestamp(self, dstBeginTimeStr) self.m_DstEndTimestamp = Internal.DstTimeStrToTimestamp(self, dstEndTimeStr) end function TimeZone:SetDstTimestamp(dstBeginTimestamp, dstEndTimestamp) self.m_DstBeginTimestamp = dstBeginTimestamp self.m_DstEndTimestamp = dstEndTimestamp end ---该时区是否有夏令时 function TimeZone:IsSupportDst() local b = (nil ~= self.m_DstBeginTimestamp) and (nil ~= self.m_DstEndTimestamp) return b end
1) 时间转时间戳
a) 公共代码
---获取没有减掉本地时区的timestamp function Internal.GetFixedTimestamp(self, date) date.isdst = false --不让os.time内部处理, 自己手动处理 local timestamp = os.time(date) --内部会减掉本地时区, 比如: UTC+8就是减8*3600 local timestampWithTzOffset = timestamp + self.m_LocalTimeZoneOffset --因为timeStr不是本地时区的时间, 把上面减掉的加回来 return timestampWithTzOffset end ---在(日期时间_当前时区)和(日期时间_夏令时区)之间选一个来计算间戳, 主要目的是确保在本地时区和夏令时区显示的时间部分保持一致。 ---比如: 对于美国东部时区(UTC-5)每天8点上班 ---1) 2024-02-10 08:00:00, 这肯定是一个非夏令时间, 会用 2024-02-10 08:00:00(EST,UTC-5)对应的时间戳 ---2) 2024-05-10 08:00:00, 这肯定是一个夏令时间, 会用 2024-05-10 08:00:00(EDT,UTC-4)对应的时间戳 ---@param self TimeZone ---@param timestampWithTzOffset integer 带有当前时区偏差的时间戳 function Internal.JudgeTimestamp(self, timestampWithTzOffset) local tzOffset = self.m_TimeZoneOffset local dstTzOffset = self.m_TimeZoneOffset + 3600 --当前时区转为夏令时区, 比如: (EST, UTC-5) -> (EDT, UTC-4)就是加1小时 local timestamp_1 = timestampWithTzOffset - tzOffset --当前时区对应的时间戳 local timestamp_2 = timestampWithTzOffset - dstTzOffset --夏令时区对应的时间戳 local isdst_1 = self:TimestampIsDst(timestamp_1) local isdst_2 = self:TimestampIsDst(timestamp_2) if isdst_1 == isdst_2 then if isdst_1 then return timestamp_2 --都是夏令时, 使用 日期时间_夏令时区 对应的时间戳 else return timestamp_1 --都不是夏令时, 使用 日期时间_当前时区 对应的时间戳 end else --一个是夏令时, 一个不是夏令时 if isdst_1 then return timestamp_2 --返回 日期时间_夏令时区 对应的时间戳, 即: 夏令时开始前的时间, 比如: 2024-03-10 02:30:00(EDT,UTC-4), 而不是02:30:00(EST,UTC-5) else return timestamp_1 --返回 日期时间_当前时区 对应的时间戳, 即: 夏令时结束后的时间, 比如: 2024-11-03 01:30:00(EST,UTC-5), 而不是01:30:00(EDT,UTC-4) end end end
b) 时间转时间戳
---@return integer timestamp function TimeZone:DateToTimestamp(date) local isdst = date.isdst local timestampWithTzOffset = Internal.GetFixedTimestamp(self, date) local timestamp = 0 --像os.time做的那样, 减掉对应的时区偏差 if true == isdst then --表示date是一个夏令时时间 local tzOffset = self.m_TimeZoneOffset + 3600 --当前时区转为夏令时, 比如: (EST, UTC-5) -> (EDT, UTC-4)就是加1小时 timestamp = timestampWithTzOffset - tzOffset --比如: (EDT, UTC-4)就是-(-4*3600) elseif false == isdst then --表示date是一个非夏令时时间 timestamp = timestampWithTzOffset - self.m_TimeZoneOffset --比如: (EST, UTC-5)就是-(-5*3600) elseif nil == isdst then if self:IsSupportDst() then timestamp = Internal.JudgeTimestamp(self, timestampWithTzOffset) else timestamp = timestampWithTzOffset - self.m_TimeZoneOffset --比如: (EST, UTC-5)就是-(-5*3600) end end return timestamp end function TimeZone:TimeStrToTimestamp(timeStr) local date = TimeUtil.TimeStrToDate(timeStr) local timestampWithTzOffset = Internal.GetFixedTimestamp(self, date) local timestamp = 0 --像os.time做的那样, 减掉timeStr所在时区的偏差 local tzOffset = date.tzOffset if tzOffset then timestamp = timestampWithTzOffset - tzOffset -- 比如: -04:00就是-(-4*3600) elseif self:IsSupportDst() then timestamp = Internal.JudgeTimestamp(self, timestampWithTzOffset) else timestamp = timestampWithTzOffset - self.m_TimeZoneOffset -- 比如: UTC-5就是-(-5*3600) end return timestamp end ---@param isdst boolean? 为nil时, 表示动态判断并在 日期时间_当前时区 和 日期时间_夏令时区 中选一个来计算时间戳 function TimeZone:TimeToTimestamp(year, month, day, hour, min, sec, isdst) local date = { year = year, month = month, day = day, hour = hour, min = min, sec = sec, isdst = isdst } local timestamp = self:DateToTimestamp(date) return timestamp end
2) 夏令时判断
function TimeZone:TimestampIsDst(timestamp) if self:IsSupportDst() then if timestamp < self.m_DstBeginTimestamp then return false end if timestamp >= self.m_DstEndTimestamp then return false end return true end return false end function TimeZone:DateIsDst(date) local timestamp = self:DateToTimestamp(date) local b = self:TimestampIsDst(timestamp) return b end function TimeZone:TimeStrIsDst(timeStr) local timestamp = self:TimeStrToTimestamp(timeStr) local b = self:TimestampIsDst(timestamp) return b end
3) 时间戳转时间
function TimeZone:TimestampToDate(timestamp) local tzOffset = self.m_TimeZoneOffset if self:TimestampIsDst(timestamp) then tzOffset = tzOffset + 3600 --当前时区转为夏令时, 比如: (EST, UTC-5) -> (EDT, UTC-4)就是加1小时 local timestampWithTzOffset = timestamp + tzOffset local date = os.date("!*t", timestampWithTzOffset) date.isdst = true return date else local timestampWithTzOffset = timestamp + tzOffset local date = os.date("!*t", timestampWithTzOffset) return date end end function TimeZone:TimestampToTimeStr(timestamp) local date = self:TimestampToDate(timestamp) local tzHour = math.floor(self.m_TimeZoneOffset / 3600) local tzMin = self.m_TimeZoneOffset - 3600 * tzHour local timeStr = "" if tzHour >= 0 then timeStr = string.format("%04d-%02d-%02d %02d:%02d:%02d%+02d:%02d", date.year, date.month, date.day, date.hour, date.min, date.sec, tzHour, tzMin) else timeStr = string.format("%04d-%02d-%02d %02d:%02d:%02d%-02d:%02d", date.year, date.month, date.day, date.hour, date.min, date.sec, -tzHour, -tzMin) end return timeStr end
TimeZone测试用例
---@type TimeZone local m_TimeZone = require("Time.TimeZone").new() m_TimeZone:SetLocalTimeZoneOffset(TimeUtil.GetLocalTimeZoneOffset()) m_TimeZone:SetTimeZoneOffset(-5 * 3600) --美国东部时区(UTC-5) m_TimeZone:SetDstByTimeStr("2024-03-10T02:00:00-05:00", "2024-11-03T02:00:00-04:00") --夏令时开始和结束时间 AssertEqual(0, m_TimeZone:GetDstBeginTimestamp() - 1710054000) AssertEqual(0, m_TimeZone:GetDstEndTimestamp() - 1730613600) AssertEqual(true, m_TimeZone:IsSupportDst()) AssertEqual(1, 1730613600 - m_TimeZone:TimeStrToTimestamp("2024-11-03 01:59:59-04:00")) local function Test_TimeStrToTimestamp() local t1 = m_TimeZone:TimeStrToTimestamp("2024-03-10 01:00:00") --肯定不是夏令时, 开始前。用 日期时间_当前时区 算时间戳 local t2 = m_TimeZone:TimeStrToTimestamp("2024-03-10 02:00:00") --可能是夏令时, 用不是夏令时的 日期时间_夏令时区 算时间戳 local t3 = m_TimeZone:TimeStrToTimestamp("2024-03-10 03:00:00") --肯定是夏令时, 开始后。用 日期时间_夏令时区 算时间戳 AssertEqual(-3600, t1 - 1710054000) AssertEqual(-3600, t2 - 1710054000) AssertEqual(0, t3 - 1710054000) local t4 = m_TimeZone:TimeStrToTimestamp("2024-11-03 00:00:00") --肯定是夏令时,结束前。用 日期时间_夏令时区 算时间戳 local t5 = m_TimeZone:TimeStrToTimestamp("2024-11-03 01:00:00") --可能是夏令时。用不是夏令时的 日期时间_夏令时区 算时间戳 local t6 = m_TimeZone:TimeStrToTimestamp("2024-11-03 02:00:00") --肯定不是夏令时, 结束后。用 日期时间_当前时区 算时间戳 AssertEqual(-7200, t4 - 1730613600) AssertEqual(0, t5 - 1730613600) AssertEqual(3600, t6 - 1730613600) end Test_TimeStrToTimestamp()
local function Test_DateToTimestamp() local t1 = m_TimeZone:DateToTimestamp({ year = 2024, month = 3, day = 10, hour = 1, min = 0, sec = 0 }) local t2 = m_TimeZone:DateToTimestamp({ year = 2024, month = 3, day = 10, hour = 2, min = 0, sec = 0 }) local t3 = m_TimeZone:DateToTimestamp({ year = 2024, month = 3, day = 10, hour = 2, min = 0, sec = 0, isdst = false }) local t4 = m_TimeZone:DateToTimestamp({ year = 2024, month = 3, day = 10, hour = 2, min = 0, sec = 0, isdst = true }) local t5 = m_TimeZone:DateToTimestamp({ year = 2024, month = 3, day = 10, hour = 3, min = 0, sec = 0 }) AssertEqual(-3600, t1 - 1710054000) AssertEqual(-3600, t2 - 1710054000) AssertEqual(0, t3 - 1710054000) AssertEqual(-3600, t4 - 1710054000) AssertEqual(0, t5 - 1710054000) local t10 = m_TimeZone:DateToTimestamp({ year = 2024, month = 11, day = 3, hour = 0, min = 0, sec = 0 }) local t11 = m_TimeZone:DateToTimestamp({ year = 2024, month = 11, day = 3, hour = 1, min = 0, sec = 0 }) local t12 = m_TimeZone:DateToTimestamp({ year = 2024, month = 11, day = 3, hour = 1, min = 0, sec = 0, isdst = false }) local t13 = m_TimeZone:DateToTimestamp({ year = 2024, month = 11, day = 3, hour = 1, min = 0, sec = 0, isdst = true }) local t14 = m_TimeZone:DateToTimestamp({ year = 2024, month = 11, day = 3, hour = 2, min = 0, sec = 0 }) AssertEqual(7200, 1730613600 - t10) AssertEqual(0, 1730613600 - t11) AssertEqual(0, 1730613600 - t12) AssertEqual(3600, 1730613600 - t13) AssertEqual(-3600, 1730613600 - t14) end Test_DateToTimestamp()
local function Test_IsDst() local b1 = m_TimeZone:TimeStrIsDst("2024-03-10 01:00:00") --肯定不是夏令时, 夏令时开始前 local b2 = m_TimeZone:TimeStrIsDst("2024-03-10 02:00:00") --不是夏令时 local b3 = m_TimeZone:TimeStrIsDst("2024-03-10 03:00:00") --肯定是夏令时, 夏令时开始后 AssertEqual(false, b1) AssertEqual(false, b2) AssertEqual(true, b3) local b4 = m_TimeZone:TimeStrIsDst("2024-11-03 00:00:00") --肯定是夏令时,夏令时结束前 local b5 = m_TimeZone:TimeStrIsDst("2024-11-03 01:00:00") --不是夏令时 local b6 = m_TimeZone:TimeStrIsDst("2024-11-03 02:00:00") --肯定不是夏令时, 夏令时结束后 AssertEqual(true, b4) AssertEqual(false, b5) AssertEqual(false, b6) end Test_IsDst()
local function Test_TimestampToDate() local date1 = m_TimeZone:TimestampToDate(1710054000 - 3600) local date2 = m_TimeZone:TimestampToDate(1710054000) local date3 = m_TimeZone:TimestampToDate(1710054000 + 3600) AssertEqual("2024-03-10 01:00:00", TimeUtil.FormatDate(date1)) AssertEqual("2024-03-10 03:00:00 dst", TimeUtil.FormatDate(date2)) AssertEqual("2024-03-10 04:00:00 dst", TimeUtil.FormatDate(date3)) local date3 = m_TimeZone:TimestampToDate(1730613600 - 3600) local date4 = m_TimeZone:TimestampToDate(1730613600 - 1) local date5 = m_TimeZone:TimestampToDate(1730613600) local date6 = m_TimeZone:TimestampToDate(1730613600 + 3600) AssertEqual("2024-11-03 01:00:00 dst", TimeUtil.FormatDate(date3)) AssertEqual("2024-11-03 01:59:59 dst", TimeUtil.FormatDate(date4)) AssertEqual("2024-11-03 01:00:00", TimeUtil.FormatDate(date5)) AssertEqual("2024-11-03 02:00:00", TimeUtil.FormatDate(date6)) end Test_TimestampToDate()

浙公网安备 33010602011771号