Module:Date: Difference between revisions

686 bytes added ,  9 years ago
rework date differences for more consistent years/months/days; differences include hours/minutes/seconds; can add 'date + diff'; tweaks
(major refactor with fixes; force Date to be read-only (error on write); list of dates in a month on a particular day of week)
(rework date differences for more consistent years/months/days; differences include hours/minutes/seconds; can add 'date + diff'; tweaks)
Line 3: Line 3:


local MINUS = '−'  -- Unicode U+2212 MINUS SIGN
local MINUS = '−'  -- Unicode U+2212 MINUS SIGN
local floor = math.floor


local Date, DateDiff, diffmt  -- forward declarations
local Date, DateDiff, diffmt  -- forward declarations
Line 34: Line 35:


local function strip_to_nil(text)
local function strip_to_nil(text)
-- If text is a string, return its trimmed content, or nil.
-- If text is a string, return its trimmed content, or nil if empty.
-- Otherwise return text (convenient when Date fields are provided from
-- Otherwise return text (convenient when Date fields are provided from
-- another module which is able to pass, for example, a number).
-- another module which may pass a string, a number, or another type).
if type(text) == 'string' then
if type(text) == 'string' then
text = text:match('(%S.-)%s*$')
text = text:match('(%S.-)%s*$')
end
end
return text
return text
end
local function number_name(number, singular, plural, sep)
-- Return the given number, converted to a string, with the
-- separator (default space) and singular or plural name appended.
plural = plural or (singular .. 's')
sep = sep or ' '
return tostring(number) .. sep .. ((number == 1) and singular or plural)
end
end


Line 65: Line 58:
end
end
return ({ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 })[month]
return ({ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 })[month]
end
local function h_m_s(time)
-- Return hour, minute, second extracted from fraction of a day.
time = floor(time * 24 * 3600 + 0.5)  -- number of seconds
local second = time % 60
time = floor(time / 60)
return floor(time / 60), time % 60, second
end
local function hms(date)
-- Return fraction of a day (0 <= fraction < 1) from date's time.
return (date.hour + (date.minute + date.second / 60) / 60) / 24
end
end


Line 76: Line 82:
--    1 January 4713 BC  = (-4712, 1, 1)  Julian calendar
--    1 January 4713 BC  = (-4712, 1, 1)  Julian calendar
--  24 November 4714 BC = (-4713, 11, 24) Gregorian calendar
--  24 November 4714 BC = (-4713, 11, 24) Gregorian calendar
local floor = math.floor
local offset
local offset
local a = floor((14 - date.month)/12)
local a = floor((14 - date.month)/12)
Line 88: Line 93:
local jd = date.day + floor((153*m + 2)/5) + 365*y + offset
local jd = date.day + floor((153*m + 2)/5) + 365*y + offset
if date.hastime then
if date.hastime then
jd = jd + (date.hour + (date.minute + date.second / 60) /60) / 24 - 0.5
jd = jd + hms(date) - 0.5
return jd, jd
return jd, jd
end
end
Line 100: 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 floor = math.floor
local calname = date.calname
local calname = date.calname
local limits -- 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 == 'Julian' then
if calname == 'Gregorian' then
limits = { -1931076.5, 5373557.49999 }
low, high = -1930999.5, 5373484.49999
elseif calname == 'Gregorian' then
elseif calname == 'Julian' then
limits = { -1930999.5, 5373484.49999 }
low, high = -1931076.5, 5373557.49999
else
else
return
return
end
end
local jd = date.jd
local jd = date.jd
if not (type(jd) == 'number' and limits[1] <= jd and jd <= limits[2]) then
if not (type(jd) == 'number' and low <= jd and jd <= high) then
return
return
end
end
local jdn = floor(jd)
local jdn = floor(jd)
if date.hastime then
if date.hastime then
local time = jd - jdn
local time = jd - jdn -- 0 <= time < 1
local hour
if time >= 0.5 then   -- if at or after midnight of next day
if time >= 0.5 then
jdn = jdn + 1
jdn = jdn + 1
time = time - 0.5
time = time - 0.5
hour = 0
else
else
hour = 12
time = time + 0.5
end
end
time = floor(time * 24 * 3600 + 0.5)  -- number of seconds after hour
date.hour, date.minute, date.second = h_m_s(time)
date.second = time % 60
time = floor(time / 60)
date.minute = time % 60
date.hour = hour + floor(time / 60)
else
else
date.second = 0
date.second = 0
Line 172: Line 170:
end
end
if H then
if H then
-- It is not possible to set M or S without also setting H.
date.hastime = true
date.hastime = true
else
else
Line 203: Line 202:
-- Valid option settings are:
-- Valid option settings are:
-- am: 'am', 'a.m.', 'AM', 'A.M.'
-- am: 'am', 'a.m.', 'AM', 'A.M.'
--    'pm', 'p.m.', 'PM', 'P.M.' (each has same meaning as corresponding item above)
-- era: 'BCMINUS', 'BCNEGATIVE', 'BC', 'B.C.', 'BCE', 'B.C.E.', 'AD', 'A.D.', 'CE', 'C.E.'
-- era: 'BCMINUS', 'BCNEGATIVE', 'BC', 'B.C.', 'BCE', 'B.C.E.', 'AD', 'A.D.', 'CE', 'C.E.'
-- Option am = 'am' does not mean the hour is AM; it means 'am' or 'pm' is used, depending on the hour.
-- Option am = 'am' does not mean the hour is AM; it means 'am' or 'pm' is used, depending on the hour,
-- Similarly, era = 'BC' means 'BC' is used if year < 0.
--    and am = 'pm' has the same meaning.
-- Similarly, era = 'BC' means 'BC' is used if year <= 0.
-- 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.
Line 212: Line 213:
result = options1
result = options1
elseif type(options1) == 'string' then
elseif type(options1) == 'string' then
-- Example: 'am:AM era:BC'
-- Example: 'am:AM era:BC' or 'am=AM era=BC'.
for item in options1:gmatch('%S+') do
for item in options1:gmatch('%S+') do
local lhs, rhs = item:match('^(%w+)[:=](.+)$')
local lhs, rhs = item:match('^(%w+)[:=](.+)$')
Line 227: