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; }