Lua 日期和时间

1,180 阅读10分钟

一、os.time(table)

用于获取相应的时间的时间戳

参数:

  • table:可选,日期表,表中使用以下字段
字段类型含义是否必须
yearnumber(整型)必须要有
monthnumber(整型)必须要有
daynumber(整型)必须要有
hournumber(整型)可选,默认为 12 (12:00:00)
minnumber(整型)可选,默认为 00
secnumber(整型)可选,默认为 00
wdaynumber(整型)本周第几天(第一天为星期天)会被忽略
ydaynumber(整型)当年中第几天(第一天为 1 月 1 日)会被忽略
isdstbool使用夏时令则为真,其余则为假可选,默认为 nil

值得注意的是,日期表中不包括时区,所以程序需要负责结合相应的时区对其正确解析。

返回值:

时间戳

1-1、使用

第一种:直接调用,不传递参数,获取当前系统的时间戳(相对于 Jan 01,1970,0:00 UTC )

print(os.time())        --> 1665536638      (现在是 2022-10-12 09:03:58)

可以将时间戳进行拆解,得到我们需要的时间

需要注意是,时间戳是基于格林尼治,而我们北京时间是东八区,所以需要增加八小时。例子中解析出来是一点,所以增加八小时就刚好九点

do
    local day2year = 365.242
    local sec2hour = 60 * 60
    local sec2day = sec2hour * 24
    local sec2year = sec2day * day2year
    function getYear(timestamp)
        -- 时间戳是从 Jan 01,1970,0:00 UTC 开始
        return timestamp // sec2year + 1970
    end
    function getHour(timestamp)
        return timestamp % sec2day // sec2hour
    end
    function getMinute(timestamp)
        return timestamp % sec2hour // 60
    end
    function getSecond(timestamp)
        return timestamp % 60
    end
end

-- 1665536638 --> 2022-10-12 09:03:58
timestamp = 1665536638
print("timestamp", timestamp)           --> timestamp	1665536638
print("year", getYear(timestamp))       --> year	2022.0
print("hour", getHour(timestamp))       --> hour	1
print("minute", getMinute(timestamp))   --> minute	3
print("second", getSecond(timestamp))   --> second	58

第二种:日期表作为参数调用 os.time ,返回值则为该表中所描述日期和时间对应的数字。

local timeTable = {
    -- 必填项 year、month、day
    year = 2022,
    month = 4,
    day = 14,
    -- 选填项 hour、minute、second,不填默认为 12:00:00
    hour = 13,
    min = 10,
    sec = 10,
    -- 选填项 isdst,默认为 nil
    isdst = false
    -- wday、yday 填了也会被忽略
}
print("isdst = false", os.time(timeTable))      --> isdst = false	1649913010

timeTable.isdst = true
print("isdst = true", os.time(timeTable))       --> isdst = true	1649909410

值得一提:

hourminsec 可以是任意值,既可以是负数,则表示回退时间,也可以是超过 24 小时或是 60 分、60 秒的值,则表示前进相应时间。

--- 表示前进了 5 天
print(os.time({ year = 1970, month = 1, day = 1, hour = 24 * 5 }))  --> 403200

二、os.date(format, time)

os.dateos.time 的反函数

参数:

  • format:期望表示形式的格式化字符串
  • time:可选,数字形式的日期和时间(如果不提供,则默认使用当前日期和时间)

举个例子

从 os.date 中解出日期表,再使用 os.time 转回时间戳,是相等的。

local time = os.time()
-- *t 的意义见下一小节
print(time, os.time(os.date("*t", time)) == time)       --> 1665706921	true

2-1、*t 生成日期表

可以给 os.dateformat 传递 *t ,获取对应时间戳的日期表

日期表的字段

字段类型含义范围
yearnumber(整型)一整年
monthnumber(整型)1-12
daynumber(整型)1-31
hournumber(整型)0-23
minnumber(整型)0-59
secnumber(整型)0-60 (因为有闰秒存在,所以上限范围可以到 60 )
wdaynumber(整型)本周第几天(第一天为星期天)1-7
ydaynumber(整型)当年中第几天(第一天为 1 月 1 日)1-366
isdstbool使用夏时令则为真,其余则为假true/false

如果不传递 time , 则会使用当前的时间戳

print("当前时间戳的日期表:")
local t = os.date("*t")
local content = "[ "
for k, v in pairs(t) do
    content = content .. tostring(k) .. " = " .. tostring(v) .. ", "
end
print(content .. " ]")  --> [ month = 10, year = 2022, wday = 5, isdst = false, yday = 286, day = 13, hour = 23, min = 1, sec = 53,  ]

如果传递 time ,则使用 time 时间戳

print("提供时间戳的日期表:")
-- 1665536638 --> 北京时间 2022-10-12 09:03:58
t = os.date("*t", 1665536638)
content = "[ "
for k, v in pairs(t) do
    content = content .. tostring(k) .. " = " .. tostring(v) .. ", "
end
print(content .. " ]")  --> [ month = 10, year = 2022, wday = 4, isdst = false, yday = 285, day = 12, hour = 9, min = 3, sec = 58,  ]

2-2、格式化日期

除了 *t ,可以使用其他的字符串,对时间戳进行格式化为相应的时间字符串。

具体可用的指示符如下:

指示符含义
%a星期几的简写(Wed)
%A星期几的全名(Wednesday)
%b月份的简写(Oct)
%B月份的全名(October)
%c日期和时间(默认)(Wed Oct 12 09:03:58 2022)
%d一个月中的第几天 [01-31]
%H24小时制中的小时数
%I12小时制中的小时数
%j一年中的第几天
%m月份
%M分钟
%p"am" 或 "pm"
%S秒数
%w星期
%W一年中的第几周
%x日期(10/12/22)
%X时间(09:03:58)
%y两位数年份(22)
%Y完整的年份(2022)
%z时区(+0800)
%%百分号(%)

1665536638 即北京时间 2022-10-12 09:03:58

local timestamp = 1665536638
print("%a 星期简称", os.date("%a", timestamp))      --> %a 星期简称	Wed
print("%A 星期全称", os.date("%A", timestamp))      --> %A 星期全称	Wednesday
print("%b 月份简写", os.date("%b", timestamp))      --> %b 月份简写	Oct
print("%B 月份全名", os.date("%B", timestamp))      --> %B 月份全名	October
print("%c 日期和时间", os.date("%c", timestamp))    --> %c 日期和时间	Wed Oct 12 09:03:58 2022
print("%d 一个月中的第几天", os.date("%d", timestamp))  --> %d 一个月中的第几天	12
print("%H 24 小时制", os.date("%H", timestamp))     --> %H 24 小时制	09
print("%I 12 小时制", os.date("%I", timestamp))     --> %I 12 小时制	09
print("%j 一年中的第几天", os.date("%j", timestamp))--> %j 一年中的第几天	285
print("%m 月份", os.date("%m", timestamp))          --> %m 月份	10
print("%M 分钟", os.date("%M", timestamp))          --> %M 分钟	03
print("%p am 或 pm", os.date("%p", timestamp))      --> %p am 或 pm	AM
print("%S 秒数", os.date("%S", timestamp))          --> %S 秒数	58
print("%w 星期", os.date("%w", timestamp))          --> %w 星期	3
print("%W 一年中的第几周", os.date("%W", timestamp))--> %W 一年中的第几周	41
print("%x 日期", os.date("%x", timestamp))          --> %x 日期	10/12/22
print("%X 时间", os.date("%X", timestamp))          --> %X 时间	09:03:58
print("%y 两位数的年份", os.date("%y", timestamp))  --> %y 两位数的年份	22
print("%Y 完整的年份", os.date("%Y", timestamp))    --> %Y 完整的年份	2022
print("%z 时区", os.date("%z", timestamp))          --> %z 时区	+0800
print("%% 百分号", os.date("%%", timestamp))        --> %% 百分号	%
print("UTC 格式", os.date("!%c", timestamp))        --> UTC 格式	Wed Oct 12 01:03:58 2022

os.date 所有的参数都不填时,默认参数为 %c 和当前时间戳。

print("默认参数", os.date())        --> 默认参数	Tue Oct 18 08:14:35 2022

格式化的字符串以 ! 开头,则 os.date 会以 UTC 格式进行解析。

-- 北京时间 2022-10-12 09:03:58
local timestamp = 1665536638
print("!%H 24 小时制", os.date("!%H", timestamp))   --> !%H 24 小时制	01
print("!%c 日期和时间", os.date("!%c", timestamp))  --> !%c 日期和时间	Wed Oct 12 01:03:58 2022

一般使用这些指示符都是为了展示需要的时间格式,下面举几个小例子:

-- 北京时间 2022-10-12 09:03:58
local timestamp = 1665536638
print(os.date("%Y/%m/%d", timestamp))               --> 2022/10/12
print(os.date("%Y-%m-%d %H:%M:%S", timestamp))      --> 2022-10-12 09:03:58
print(os.date("%Y-%j", timestamp))                  --> 2022-285

三、时间和日期的处理

前面有提到给到 os.time 的日期表的各个字段是可以不受范围约束的,可以使用这一规则进行向前回退时间或向后推进时间。

而使用通过 os.date 使用 *t 产出的日期表,则所有字段都是在范围内的(各个字段的范围可以参考第二节的表)

所以我们可以利用此特性进行一些时间的变化。

利用这一特性,我们可以不用去计算各个月是多少天,每天是多少秒,只需要将需要推进或后退的时间叠加上即可

-- 北京时间 2022-10-12 09:03:58
local timestamp = 1665536638
print(timestamp)
print("原始时间", os.date("%Y/%m/%d", timestamp))              --> 原始时间	2022/10/12

local currentTable = os.date("*t", timestamp)

-- 向前推 40 天
currentTable.day = currentTable.day - 40
print("回退 40 天", os.date("%Y/%m/%d", os.time(currentTable)))  --> 回退 40 天	2022/09/02

-- 向后 6 个月
currentTable = os.date("*t", timestamp)
currentTable.month = currentTable.month + 6
print("推进 6 个月", os.date("%Y/%m/%d", os.time(currentTable)))  --> 推进 6 个月	2023/04/12

值得注意

如果碰上日期 3 月 31 号 这样的日期,前进一个月则为 4 月 31 号 ,其实会被归一化为 5 月 1 号

但如果将 5 月 1 号 回退一个月,则会为 4 月 1 号 ,和原本的日期就会不一样了。

-- 这里的转换会有问题(由于日历机制导致)
-- 3 月 31 号 + 1 个月 --> 4 月 31 号 --> 5 月 1 号
-- 5 月 1 号 - 1 个月 --> 4 月 1 号
local t = { year = 2022, month = 3, day = 31 }
print("原始日期", os.date("%Y-%m-%d", os.time(t)))      --> 原始日期	2022-03-31

t.month = t.month + 1
print("推进 1 个月",os.date("%Y-%m-%d", os.time(t)))    --> 推进 1 个月	2022-05-01 

t.month = t.month - 1
print("后退 1 个月",os.date("%Y-%m-%d", os.time(t)))    --> 后退 1 个月	2022-04-01

四、os.difftime(t2, t1)

用于计算两个时间之间的差值(单位为秒),计算公式为 t2 - t1

local startTime = os.time({ year = 2022, month = 1, day = 1 })
-- 北京时间 2022-10-12 09:03:58
local endTime = 1665536638
local diff = os.difftime(endTime, startTime)
print(string.format("%s 到 %s 相差 %s", startTime, endTime, diff))      --> 1641009600 到 1665536638 相差 24527038.0

五、获取毫秒 os.clock()

我们会发现前面获取的时间都是以秒作为单位,但有时我们需要统计代码的运行耗时,则需要精确到毫秒。则此时我们需要使用 os.clock

os.clock 返回程序使用的 CPU 时间的近似值,以秒为单位但是是一个浮点数,可以通过获取小数部分,得到更高精度的时间(毫秒)。

举一个常用的统计时间例子

local function someLogic()
    -- 一些需要统计时间的逻辑
    local s = 0
    for i = 1, 100000000 do
        s = s + i
    end
end

local startTime = os.clock()
someLogic()
print(string.format("耗费时间:%.2f", os.clock() - startTime))  --> 耗费时间:0.54

六、写在最后

Lua 项目地址:Github传送门 (如果对你有所帮助或喜欢的话,赏个star吧,码字不易,请多多支持)

如果觉得本篇博文对你有所启发或是解决了困惑,点个赞或关注我呀

公众号搜索 “江澎涌”,更多优质文章会第一时间分享与你。

image.png