Module:Date: Difference between revisions

2,645 bytes added ,  9 years ago
date:list() now accepts a count; DateDiff methods age and duration; cleaner member names; remember if time entered with an am/pm option for default output
(rework date differences for more consistent years/months/days; differences include hours/minutes/seconds; can add 'date + diff'; tweaks)
(date:list() now accepts a count; DateDiff methods age and duration; cleaner member names; remember if time entered with an am/pm option for default output)
Line 85: Line 85:
local a = floor((14 - date.month)/12)
local a = floor((14 - date.month)/12)
local y = date.year + 4800 - a
local y = date.year + 4800 - a
if date.calname == 'Julian' then
if date.calendar == 'Julian' then
offset = floor(y/4) - 32083
offset = floor(y/4) - 32083
else
else
Line 105: Line 105:
-- This handles the proleptic Julian and Gregorian calendars.
-- This handles the proleptic Julian and Gregorian calendars.
-- Negative Julian dates are not defined but they work.
-- Negative Julian dates are not defined but they work.
local calname = date.calname
local calname = date.calendar
local low, high  -- min/max limits for date ranges −9999-01-01 to 9999-12-31
local low, high  -- min/max limits for date ranges −9999-01-01 to 9999-12-31
if calname == 'Gregorian' then
if calname == 'Gregorian' then
Line 166: Line 166:
(-9999 <= y and y <= 9999 and
(-9999 <= y and y <= 9999 and
1 <= m and m <= 12 and
1 <= m and m <= 12 and
1 <= d and d <= days_in_month(y, m, date.calname)) then
1 <= d and d <= days_in_month(y, m, date.calendar)) then
return
return
end
end
Line 199: Line 199:
-- If options1 is a string, return a table with its settings, or
-- If options1 is a string, return a table with its settings, or
-- if it is a table, use its settings.
-- if it is a table, use its settings.
-- Missing options are set from options2 or defaults.
-- Missing options are set from table options2 or defaults.
-- If a default is used, a flag is set so caller knows the value was not intentionally set.
-- Valid option settings are:
-- Valid option settings are:
-- am: 'am', 'a.m.', 'AM', 'A.M.'
-- am: 'am', 'a.m.', 'AM', 'A.M.'
Line 209: Line 210:
-- BCMINUS displays a MINUS if year < 0 and the display format does not include %{era}.
-- BCMINUS displays a MINUS if year < 0 and the display format does not include %{era}.
-- BCNEGATIVE is similar but displays a hyphen.
-- BCNEGATIVE is similar but displays a hyphen.
local result = {}
local result = { bydefault = {} }
if type(options1) == 'table' then
if type(options1) == 'table' then
result = options1
result.am = options1.am
result.era = options1.era
elseif type(options1) == 'string' then
elseif type(options1) == 'string' then
-- Example: 'am:AM era:BC' or 'am=AM era=BC'.
-- Example: 'am:AM era:BC' or 'am=AM era=BC'.
Line 224: Line 226:
local defaults = { am = 'am', era = 'BC' }
local defaults = { am = 'am', era = 'BC' }
for k, v in pairs(defaults) do
for k, v in pairs(defaults) do
result[k] = result[k] or options2[k] or v
if not result[k] then
if options2[k] then
result[k] = options2[k]
else
result[k] = v
result.bydefault[k] = true
end
end
end
end
return result
return result
Line 287: Line 296:
M = { fmt = '%02d', fmt2 = '%d', field = 'minute' },
M = { fmt = '%02d', fmt2 = '%d', field = 'minute' },
S = { fmt = '%02d', fmt2 = '%d', field = 'second' },
S = { fmt = '%02d', fmt2 = '%d', field = 'second' },
j = { fmt = '%03d', fmt2 = '%d', field = 'doy' },
j = { fmt = '%03d', fmt2 = '%d', field = 'dayofyear' },
I = { fmt = '%02d', fmt2 = '%d', field = 'hour', special = 'hour12' },
I = { fmt = '%02d', fmt2 = '%d', field = 'hour', special = 'hour12' },
p = { field = 'hour', special = 'am' },
p = { field = 'hour', special = 'am' },
Line 372: Line 381:
end
end
if type(fmt) ~= 'string' then
if type(fmt) ~= 'string' then
fmt = '%-d %B %-Y %{era}'
fmt = 'dmy'
if date.hastime then
if date.hastime then
if date.second > 0 then
fmt = (date.second > 0 and 'hms ' or 'hm ') .. fmt
fmt = '%H:%M:%S ' .. fmt
else
fmt = '%H:%M ' .. fmt
end
end
end
elseif fmt:find('%', 1, true) then
return strftime(date, fmt, options)
return strftime(date, fmt, options)
end
end
if fmt:find('%', 1, true) then
local function hm_fmt()
return strftime(date, fmt, options)
local plain = make_option_table(options, date.options).bydefault.am
return plain and '%H:%M' or '%-I:%M %p'
end
end
local need_time = date.hastime
local t = collection()
local t = collection()
for item in fmt:gmatch('%S+') do
for item in fmt:gmatch('%S+') do
local f
local f
if item == 'hm' then
if item == 'hm' then
f = '%H:%M'
f = hm_fmt()
need_time = false
elseif item == 'hms' then
elseif item == 'hms' then
f = '%H:%M:%S'
f = '%H:%M:%S'
need_time = false
elseif item == 'ymd' then
elseif item == 'ymd' then
f = '%Y-%m-%d %{era}'
f = '%Y-%m-%d %{era}'
Line 403: Line 413:
t:add(f)
t:add(f)
end
end
return strftime(date, t:join(' '), options)
fmt = t:join(' ')
if need_time then
fmt = hm_fmt() .. ' ' .. fmt
end
return strftime(date, fmt, options)
end
end


Line 480: Line 494:
end
end


local function _make_list(date, spec)
local function _date_list(date, spec)
-- Return a possibly empty numbered table of dates meeting the specification.
-- Return a possibly empty numbered table of dates meeting the specification.
-- The spec should be a string like "Tue >=" meaning that the list will
-- Dates in the list are in ascending order (oldest date first).
-- hold dates for all Tuesdays on or after date, and in date's month.
-- The spec should be a string of form "<count> <day> <op>"
-- where each item is optional and
--  count = number of items wanted in list
--   day = abbreviation or name such as Mon or Monday
--  op = >, >=, <, <= (default is > meaning after date)
-- If no count is given, the list is for the specified days in date's month.
-- The default day is date's day.
-- The spec can also be a positive or negative number:
--  -5 is equivalent to '5 <'
--  5  is equivalent to '5' which is '5 >'