From: daurnimator Date: Mon, 21 Jul 2014 04:41:08 +0000 (-0900) Subject: Add strftime X-Git-Url: https://git.madduck.net/etc/awesome.git/commitdiff_plain/85c40964551cb74c3d6ef6bf61a12fd79013389b?ds=sidebyside Add strftime --- diff --git a/luatz-scm-0.rockspec b/luatz-scm-0.rockspec index ab6f212..6324360 100644 --- a/luatz-scm-0.rockspec +++ b/luatz-scm-0.rockspec @@ -27,6 +27,7 @@ build = { ["luatz.gettime"] = "luatz/gettime.lua" ; ["luatz.parse"] = "luatz/parse.lua" ; ["luatz.timetable"] = "luatz/timetable.lua" ; + ["luatz.strftime"] = "luatz/strftime.lua" ; ["luatz.tzcache"] = "luatz/tzcache.lua" ; ["luatz.tzfile"] = "luatz/tzfile.lua" ; ["luatz.tzinfo"] = "luatz/tzinfo.lua" ; diff --git a/luatz/init.lua b/luatz/init.lua index b3ca99e..2660715 100644 --- a/luatz/init.lua +++ b/luatz/init.lua @@ -1,5 +1,6 @@ local _M = { parse = require "luatz.parse" ; + strftime = require "luatz.strftime" ; timetable = require "luatz.timetable" ; } diff --git a/luatz/strftime.lua b/luatz/strftime.lua new file mode 100644 index 0000000..0f6a26f --- /dev/null +++ b/luatz/strftime.lua @@ -0,0 +1,203 @@ +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 i, 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 ( ) + 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 ( ) + 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 ( ) + return "+0000" +end +function t:Z ( ) + return "GMT" +end +-- A literal '%' character. +t["%"] = function ( self ) + 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 + +return { + strftime = strftime ; +} diff --git a/luatz/timetable.lua b/luatz/timetable.lua index 3bb7689..6cded31 100644 --- a/luatz/timetable.lua +++ b/luatz/timetable.lua @@ -143,6 +143,15 @@ function timetable_methods:rfc_3339 ( ) return strformat ( "%04u-%02u-%02uT%02u:%02u:%06.3f" , self:unpack ( ) ) end +local strftime = require "luatz.strftime".strftime +function timetable_methods:strftime ( format_string ) + return strftime ( format_string , self ) +end + +function timetable_methods:asctime ( ) + return self:strftime ( "%c\n" ) +end + local timetable_mt = { __index = timetable_methods ; __tostring = timetable_methods.rfc_3339 ; diff --git a/spec/strftime_spec.lua b/spec/strftime_spec.lua new file mode 100644 index 0000000..6d019c9 --- /dev/null +++ b/spec/strftime_spec.lua @@ -0,0 +1,24 @@ +local luatz = require "luatz.init" + +describe("strftime", function() + local strftime = luatz.strftime.strftime + local time = luatz.time() + for i, spec in ipairs { + "a", "A", "b", "B", "c", "C", "d", "D", "e", "F", + "g", "G", "H", "I", "j", "m", "M", "n", "p", "r", + "R", --[["s",]] "S", "t", "T", "u", "U", "V", "w", "W", + "y", "Y", "z", "Z" , "%" + } do + local tt = luatz.gmtime(time) + it("Specifier "..spec.." is equivalent to os.date", function() + local f = "%"..spec + local osdf = "!%"..spec + for i=1, 365*12 do + local t = time + 60*60*24*i + tt.day = tt.day + 1 + tt:normalise ( ) + assert.are.same(os.date(osdf,t), strftime(f,tt)) + end + end) + end +end)