You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
207 lines
5.5 KiB
Lua
207 lines
5.5 KiB
Lua
local strformat = string.format
|
|
local floor = math.floor
|
|
local function idiv(n, d)
|
|
return floor(n / d)
|
|
end
|
|
|
|
local c_locale = {
|
|
abday = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"},
|
|
day = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
|
|
abmon = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"},
|
|
mon = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November",
|
|
"December"},
|
|
am_pm = {"AM", "PM"},
|
|
}
|
|
|
|
--- ISO-8601 week logic
|
|
-- ISO 8601 weekday as number with Monday as 1 (1-7)
|
|
local function iso_8601_weekday(wday)
|
|
if wday == 1 then
|
|
return 7
|
|
else
|
|
return wday - 1
|
|
end
|
|
end
|
|
local iso_8601_week
|
|
do
|
|
-- Years that have 53 weeks according to ISO-8601
|
|
local long_years = {}
|
|
for _, v in ipairs {4, 9, 15, 20, 26, 32, 37, 43, 48, 54, 60, 65, 71, 76, 82, 88, 93, 99, 105, 111, 116, 122, 128,
|
|
133, 139, 144, 150, 156, 161, 167, 172, 178, 184, 189, 195, 201, 207, 212, 218, 224, 229, 235,
|
|
240, 246, 252, 257, 263, 268, 274, 280, 285, 291, 296, 303, 308, 314, 320, 325, 331, 336, 342,
|
|
348, 353, 359, 364, 370, 376, 381, 387, 392, 398} do
|
|
long_years[v] = true
|
|
end
|
|
local function is_long_year(year)
|
|
return long_years[year % 400]
|
|
end
|
|
function iso_8601_week(self)
|
|
local wday = iso_8601_weekday(self.wday)
|
|
local n = self.yday - wday
|
|
local year = self.year
|
|
if n < -3 then
|
|
year = year - 1
|
|
if is_long_year(year) then
|
|
return year, 53, wday
|
|
else
|
|
return year, 52, wday
|
|
end
|
|
elseif n >= 361 and not is_long_year(year) then
|
|
return year + 1, 1, wday
|
|
else
|
|
return year, idiv(n + 10, 7), wday
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Specifiers
|
|
local t = {}
|
|
function t:a(locale)
|
|
return "%s", locale.abday[self.wday]
|
|
end
|
|
function t:A(locale)
|
|
return "%s", locale.day[self.wday]
|
|
end
|
|
function t:b(locale)
|
|
return "%s", locale.abmon[self.month]
|
|
end
|
|
function t:B(locale)
|
|
return "%s", locale.mon[self.month]
|
|
end
|
|
function t:c(locale)
|
|
return "%.3s %.3s%3d %.2d:%.2d:%.2d %d", locale.abday[self.wday], locale.abmon[self.month], self.day, self.hour,
|
|
self.min, self.sec, self.year
|
|
end
|
|
-- Century
|
|
function t:C()
|
|
return "%02d", idiv(self.year, 100)
|
|
end
|
|
function t:d()
|
|
return "%02d", self.day
|
|
end
|
|
-- Short MM/DD/YY date, equivalent to %m/%d/%y
|
|
function t:D()
|
|
return "%02d/%02d/%02d", self.month, self.day, self.year % 100
|
|
end
|
|
function t:e()
|
|
return "%2d", self.day
|
|
end
|
|
-- Short YYYY-MM-DD date, equivalent to %Y-%m-%d
|
|
function t:F()
|
|
return "%d-%02d-%02d", self.year, self.month, self.day
|
|
end
|
|
-- Week-based year, last two digits (00-99)
|
|
function t:g()
|
|
return "%02d", iso_8601_week(self) % 100
|
|
end
|
|
-- Week-based year
|
|
function t:G()
|
|
return "%d", iso_8601_week(self)
|
|
end
|
|
t.h = t.b
|
|
function t:H()
|
|
return "%02d", self.hour
|
|
end
|
|
function t:I()
|
|
return "%02d", (self.hour - 1) % 12 + 1
|
|
end
|
|
function t:j()
|
|
return "%03d", self.yday
|
|
end
|
|
function t:m()
|
|
return "%02d", self.month
|
|
end
|
|
function t:M()
|
|
return "%02d", self.min
|
|
end
|
|
-- New-line character ('\n')
|
|
function t:n() -- luacheck: ignore 212
|
|
return "\n"
|
|
end
|
|
function t:p(locale)
|
|
return self.hour < 12 and locale.am_pm[1] or locale.am_pm[2]
|
|
end
|
|
-- TODO: should respect locale
|
|
function t:r(locale)
|
|
return "%02d:%02d:%02d %s", (self.hour - 1) % 12 + 1, self.min, self.sec,
|
|
self.hour < 12 and locale.am_pm[1] or locale.am_pm[2]
|
|
end
|
|
-- 24-hour HH:MM time, equivalent to %H:%M
|
|
function t:R()
|
|
return "%02d:%02d", self.hour, self.min
|
|
end
|
|
function t:s()
|
|
return "%d", self:timestamp()
|
|
end
|
|
function t:S()
|
|
return "%02d", self.sec
|
|
end
|
|
-- Horizontal-tab character ('\t')
|
|
function t:t() -- luacheck: ignore 212
|
|
return "\t"
|
|
end
|
|
-- ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S
|
|
function t:T()
|
|
return "%02d:%02d:%02d", self.hour, self.min, self.sec
|
|
end
|
|
function t:u()
|
|
return "%d", iso_8601_weekday(self.wday)
|
|
end
|
|
-- Week number with the first Sunday as the first day of week one (00-53)
|
|
function t:U()
|
|
return "%02d", idiv(self.yday - self.wday + 7, 7)
|
|
end
|
|
-- ISO 8601 week number (00-53)
|
|
function t:V()
|
|
return "%02d", select(2, iso_8601_week(self))
|
|
end
|
|
-- Weekday as a decimal number with Sunday as 0 (0-6)
|
|
function t:w()
|
|
return "%d", self.wday - 1
|
|
end
|
|
-- Week number with the first Monday as the first day of week one (00-53)
|
|
function t:W()
|
|
return "%02d", idiv(self.yday - iso_8601_weekday(self.wday) + 7, 7)
|
|
end
|
|
-- TODO make t.x and t.X respect locale
|
|
t.x = t.D
|
|
t.X = t.T
|
|
function t:y()
|
|
return "%02d", self.year % 100
|
|
end
|
|
function t:Y()
|
|
return "%d", self.year
|
|
end
|
|
-- TODO timezones
|
|
function t:z() -- luacheck: ignore 212
|
|
return "+0000"
|
|
end
|
|
function t:Z() -- luacheck: ignore 212
|
|
return "GMT"
|
|
end
|
|
-- A literal '%' character.
|
|
t["%"] = function(self) -- luacheck: ignore 212
|
|
return "%%"
|
|
end
|
|
|
|
local function strftime(format_string, timetable)
|
|
return (string.gsub(format_string, "%%([EO]?)(.)", function(locale_modifier, specifier)
|
|
local func = t[specifier]
|
|
if func then
|
|
return strformat(func(timetable, c_locale))
|
|
else
|
|
error("invalid conversation specifier '%" .. locale_modifier .. specifier .. "'", 3)
|
|
end
|
|
end))
|
|
end
|
|
|
|
local function asctime(timetable)
|
|
-- Equivalent to the format string "%c\n"
|
|
return strformat(t.c(timetable, c_locale)) .. "\n"
|
|
end
|
|
|
|
return {
|
|
strftime = strftime,
|
|
asctime = asctime,
|
|
}
|