Jump to content

Module:Date: Difference between revisions

3,539 bytes added ,  8 years ago
tweak formatted output; extract_date to parse an input date string
(formatted output and strftime)
(tweak formatted output; extract_date to parse an input date string)
Line 152: Line 152:
date.month = m + 3 - 12*floor(m/10)
date.month = m + 3 - 12*floor(m/10)
date.year = 100*b + d - 4800 + floor(m/10)
date.year = 100*b + d - 4800 + floor(m/10)
end
local function set_date_from_numbers(date, numbers, options)
-- Set the fields of table date from numeric values.
-- Return true if date is valid.
if type(numbers) ~= 'table' then
return
end
local y = numbers.y or numbers[1]
local m = numbers.m or numbers[2]
local d = numbers.d or numbers[3]
local H = numbers.H or numbers[4]
local M = numbers.M or numbers[5] or 0
local S = numbers.S or numbers[6] or 0
if not (y and m and d) then
return
end
if not (-9999 <= y and y <= 9999 and 1 <= m and m <= 12 and
1 <= d and d <= days_in_month(y, m, date.calname)) then
return
end
if H then
date.hastime = true
else
H = 0
end
if not (0 <= H and H <= 23 and
0 <= M and M <= 59 and
0 <= S and S <= 59) then
return
end
date.year = y    -- -9999 to 9999 ('n BC' → year = 1 - n)
date.month = m  -- 1 to 12
date.day = d    -- 1 to 31
date.hour = H    -- 0 to 59
date.minute = M  -- 0 to 59
date.second = S  -- 0 to 59
date.isvalid = true
if type(options) == 'table' then
for _, k in ipairs({ 'am', 'era' }) do
if options[k] then
date.options[k] = options[k]
end
end
end
return true
end
end


Line 177: Line 223:
return '(invalid)'
return '(invalid)'
end
end
local shortcuts = {
['%c'] = '%-I:%M %p %-d %B %Y%{era}',  -- date and time: 2:30 pm 1 April 2016
['%x'] = '%-d %B %Y%{era}',            -- date:          1 April 2016
['%X'] = '%-I:%M %p',                  -- time:          2:30 pm
}
local codes = {
local codes = {
a = { field = 'dayabbr' },
a = { field = 'dayabbr' },
Line 184: Line 235:
u = { fmt = '%d'  , field = 'dowiso' },
u = { fmt = '%d'  , field = 'dowiso' },
w = { fmt = '%d'  , field = 'dow' },
w = { fmt = '%d'  , field = 'dow' },
d = { fmt = '%02d', field = 'day' },
d = { fmt = '%02d', fmt2 = '%d', field = 'day' },
m = { fmt = '%02d', field = 'month' },
m = { fmt = '%02d', fmt2 = '%d', field = 'month' },
Y = { fmt = '%04d', field = 'year' },
Y = { fmt = '%04d', fmt2 = '%d', field = 'year' },
H = { fmt = '%02d', field = 'hour' },
H = { fmt = '%02d', fmt2 = '%d', field = 'hour' },
M = { fmt = '%02d', field = 'minute' },
M = { fmt = '%02d', fmt2 = '%d', field = 'minute' },
S = { fmt = '%02d', field = 'second' },
S = { fmt = '%02d', fmt2 = '%d', field = 'second' },
j = { fmt = '%03d', field = 'doy' },
j = { fmt = '%03d', fmt2 = '%d', field = 'doy' },
I = { fmt = '%02d', field = 'hour', special = 'hour12' },
I = { fmt = '%02d', fmt2 = '%d', field = 'hour', special = 'hour12' },
p = { field = 'hour', special = 'am' },
p = { field = 'hour', special = 'am' },
X = { fmt = '%02d:%02d:%02d', field = 'hour', special = 'hms' },
['-d'] = { fmt = '%d', field = 'day' },
['-m'] = { fmt = '%d', field = 'month' },
['-Y'] = { fmt = '%d', field = 'year' },
['-j'] = { fmt = '%d', field = 'doy' },
}
}
options = make_option_table(options or date.options)
options = make_option_table(options or date.options)
local amopt = options.am
local amopt = options.am
local eraopt = options.era
local eraopt = options.era
local function replace_code(id)
local function replace_code(modifier, id)
local code = codes[id]
local code = codes[id]
if code then
if code then
local fmt = code.fmt
local fmt = code.fmt
if modifier == '-' and code.fmt2 then
fmt = code.fmt2
end
local value = date[code.field]
local value = date[code.field]
local special = code.special
local special = code.special
Line 212: Line 261:
value = value % 12
value = value % 12
value = value == 0 and 12 or value
value = value == 0 and 12 or value
elseif special == 'hms' then
return string.format(fmt, value, date.minute, date.second)
elseif special == 'am' then
elseif special == 'am' then
local ap = ({
local ap = ({
Line 258: Line 305:
-- This occurs, for example, if id is the name of a function.
-- This occurs, for example, if id is the name of a function.
return nil
return nil
end
if shortcuts[format] then
format = shortcuts[format]
end
end
local PERCENT = '\127PERCENT\127'
local PERCENT = '\127PERCENT\127'
Line 263: Line 313:
:gsub('%%%%', PERCENT)
:gsub('%%%%', PERCENT)
:gsub('%%{(%w+)}', replace_property)
:gsub('%%{(%w+)}', replace_property)
:gsub('%%(-?%a)', replace_code)
:gsub('%%(-?)(%a)', replace_code)
:gsub(PERCENT, '%%')
:gsub(PERCENT, '%%')
)
)
Line 402: Line 452:
['C.E.']      = { 'B.C.E.', 'C.E.' },
['C.E.']      = { 'B.C.E.', 'C.E.' },
}
}
local function extract_date(text)
-- Parse the date/time in text and return n, o where
--  n = table of numbers with date/time fields
--  o = table of options for AM/PM or AD/BC, if any
-- or return nothing if date is known to be invalid.
-- Caller determines if the values in n are valid.
-- Dates of form d/m/y, m/d/y, y/m/d are rejected as ambiguous and undesirable.
local date, options = {}, {}
local function extract_ymd(item)
local ystr, mstr, dstr = item:match('^(%d%d%d%d)-(%w+)-(%d%d?)$')
if ystr then
local m
if mstr:match('^%d%d?$') then
m = tonumber(mstr)
else
m = month_number(mstr)
end
if m then
date.y = tonumber(ystr)
date.m = m
date.d = tonumber(dstr)
return true
end
end
end
local function extract_month(item)
-- A month must be given as a name or abbreviation; a number would be ambiguous.
local m = month_number(item)
if m then
date.m = m
return true
end
end
local function extract_time(item)
local h, m, s = item:match('^(%d%d?):(%d%d)(:?%d*)$')
if date.H or not h then
return
end
if s ~= '' then
s = s:match('^:(%d%d)$')
if not s then
return
end
end
date.H = tonumber(h)
date.M = tonumber(m)
date.S = tonumber(s)  -- nil if empty string
return true
end
local ampm_options = {
['am']  = 'am',
['AM']  = 'AM',
['a.m.'] = 'a.m.',
['A.M.'] = 'A.M.',
['pm']  = 'am',  -- same as am
['PM']  = 'AM',
['p.m.'] = 'a.m.',
['P.M.'] = 'A.M.',
}
local item_count = 0
local index_time
local function set_ampm(item)
local H = date.H
if H and not options.am and index_time + 1 == item_count then
options.am = ampm_options[item]
if item:match('^[Aa]') then
if not (1 <= H and H <= 12) then
return
end
if H == 12 then
date.H = 0
end
else
if not (1 <= H and H <= 23) then
return
end
if H <= 11 then
date.H = H + 12
end
end
return true
end
end
for item in text:gsub(',', ' '):gmatch('%S+') do
-- Accept options in peculiar places; if duplicated, last wins.
item_count = item_count + 1
if era_text[item] then
options.era = item
elseif ampm_options[item] then
if not set_ampm(item) then
return
end
elseif item:find(':', 1, true) then
if not extract_time(item) then
return
end
index_time = item_count
elseif date.d and date.m then
if date.y then
return  -- should be nothing more so item is invalid
end
if not item:match('^(%d%d?%d?%d?)$') then
return
end
date.y = tonumber(item)
elseif date.d then
if not extract_month(item) then
return
end
elseif date.m then
if not item:match('^(%d%d?)$') then
return
end
date.d = tonumber(item)
elseif not extract_ymd(item) then
if item:match('^(%d%d?)$') then
date.d = tonumber(item)
elseif not extract_month(item) then
return
end
end
end
return date, options
end


-- Metatable for some operations on dates.
-- Metatable for some operations on dates.
Line 469: Line 644:


--[[ Examples of syntax to construct a date:
--[[ Examples of syntax to construct a date:
Date(y, m, d, 'julian') default calendar is 'gregorian'
Date(y, m, d, 'julian')             default calendar is 'gregorian'
Date(y, m, d, H, M, S, 'julian')
Date(y, m, d, H, M, S, 'julian')
Date('juliandate', jd, 'julian')    if jd contains "." text output includes H:M:S
Date('juliandate', jd, 'julian')    if jd contains "." text output includes H:M:S
Line 475: Line 650:
Date('currentdatetime')
Date('currentdatetime')
LATER: Following are not yet implemented:
LATER: Following are not yet implemented:
Date('currentdate', H, M, S)     current date with given time
Date('currentdate', H, M, S)       current date with given time
Date('1 April 1995', 'julian')     parse date from text
Date('1 April 1995', 'julian')     parse date from text
Date('1 April 1995 AD', 'julian')  AD, CE, BC, BCE (using one of these sets a flag to do same for output)
Date('1 April 1995 AD', 'julian')  AD, CE, BC, BCE (using one of these sets a flag to do same for output)
Date('04:30:59 1 April 1995', 'julian')
Date('04:30:59 1 April 1995', 'julian')
Line 500: Line 675:
text = date_text,
text = date_text,
}
}
local argtype, datetext
local numbers = collection()
local numbers = collection()
local datetext
local argtype
for _, v in ipairs({...}) do
for _, v in ipairs({...}) do
v = strip_to_nil(v)
v = strip_to_nil(v)
Line 546: Line 720:
end
end
if argtype == 'datetext' then
if argtype == 'datetext' then
if numbers.n > 0 then
if not (numbers.n == 0 and
set_date_from_numbers(result,
extract_date(datetext))) then
return {}
return {}
end
end
-- TODO Parse datetext to extract y,m,d,H,M,S.
elseif argtype == 'juliandate' then
elseif argtype == 'juliandate' then
if numbers.n == 1 then
if numbers.n == 1 then
Line 570: Line 745:
result.isvalid = true
result.isvalid = true
elseif argtype == 'setdate' then
elseif argtype == 'setdate' then
if not (3 <= numbers.n and numbers.n <= 6) then
if not set_date_from_numbers(result, numbers) then
return {}
return {}
end
end
local y, m, d = numbers[1], numbers[2], numbers[3]
if not (-9999 <= y and y <= 9999 and 1 <= m and m <= 12 and
1 <= d and d <= days_in_month(y, m, result.calname)) then
return {}
end
local H = numbers[4]
if H then
result.hastime = true
else
H = 0
end
local M = numbers[5] or 0
local S = numbers[6] or 0
if not (0 <= H and H <= 23 and
0 <= M and M <= 59 and
0 <= S and S <= 59) then
return {}
end
result.year = y    -- -9999 to 9999; '1 BC' → year = 0; 'n BC' → year = 1 - n
result.month = m  -- 1 to 12
result.day = d    -- 1 to 31
result.hour = H    -- 0 to 59
result.minute = M  -- 0 to 59
result.second = S  -- 0 to 59
result.isvalid = true
else
else
return {}
return {}
Anonymous user
Cookies help us deliver our services. By using our services, you agree to our use of cookies.