Diferencia entre revisiones de «Módulo:Date»

De Hispanopedia
Sin resumen de edición
Supremo (discusión | contribs.)
m 35 revisiones importadas
 
(No se muestran 38 ediciones intermedias de 5 usuarios)
Línea 1: Línea 1:
-- Date functions for use by other modules.
-- Date functions for use by other modules.
-- I18N and time zones are not supported.
-- I18N and time zones are not supported.


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


local Date, DateDiff, diffmt -- forward declarations
local Date, DateDiff, diffmt -- forward declarations
local uniq = { 'unique identifier' }
local uniq = { 'unique identifier' }


local function is_date(t)
local function is_date(t)
  -- The system used to make a date read-only means there is no unique
-- The system used to make a date read-only means there is no unique
  -- metatable that is conveniently accessible to check.
-- metatable that is conveniently accessible to check.
  return type(t) == 'table' and t._id == uniq
return type(t) == 'table' and t._id == uniq
end
end


local function is_diff(t)
local function is_diff(t)
  return type(t) == 'table' and getmetatable(t) == diffmt
return type(t) == 'table' and getmetatable(t) == diffmt
end
end


local function _list_join(list, sep)
local function _list_join(list, sep)
  return table.concat(list, sep)
return table.concat(list, sep)
end
end


local function collection()
local function collection()
  -- Return a table to hold items.
-- Return a table to hold items.
  return {
return {
    n = 0,
n = 0,
    add = function (self, item)
add = function (self, item)
      self.n = self.n + 1
self.n = self.n + 1
      self[self.n] = item
self[self.n] = item
    end,
end,
    join = _list_join,
join = _list_join,
  }
}
end
end


local function strip_to_nil(text)
local function strip_to_nil(text)
  -- If text is a string, return its trimmed content, or nil if empty.
-- 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 may pass a string, a number, or another type).
-- 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
end


local function is_leap_year(year, calname)
local function is_leap_year(year, calname)
  -- Return true if year is a leap year.
-- Return true if year is a leap year.
  if calname == 'Juliano' then
if calname == 'Julian' then
    return year % 4 == 0
return year % 4 == 0
  end
end
  return (year % 4 == 0 and year % 100 ~= 0) or year % 400 == 0
return (year % 4 == 0 and year % 100 ~= 0) or year % 400 == 0
end
end


local function days_in_month(year, month, calname)
local function days_in_month(year, month, calname)
  -- Return number of days (1..31) in given month (1..12).
-- Return number of days (1..31) in given month (1..12).
  if month == 2 and is_leap_year(year, calname) then
if month == 2 and is_leap_year(year, calname) then
    return 29
return 29
  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
end


local function h_m_s(time)
local function h_m_s(time)
  -- Return hour, minute, second extracted from fraction of a day.
-- Return hour, minute, second extracted from fraction of a day.
  time = floor(time * 24 * 3600 + 0.5) -- number of seconds
time = floor(time * 24 * 3600 + 0.5) -- number of seconds
  local second = time % 60
local second = time % 60
  time = floor(time / 60)
time = floor(time / 60)
  return floor(time / 60), time % 60, second
return floor(time / 60), time % 60, second
end
end


local function hms(date)
local function hms(date)
  -- Return fraction of a day from date's time, where (0 <= fraction < 1)
-- Return fraction of a day from date's time, where (0 <= fraction < 1)
  -- if the values are valid, but could be anything if outside range.
-- if the values are valid, but could be anything if outside range.
  return (date.hour + (date.minute + date.second / 60) / 60) / 24
return (date.hour + (date.minute + date.second / 60) / 60) / 24
end
end


local function julian_date(date)
local function julian_date(date)
  -- Return jd, jdz from a Julian or Gregorian calendar date where
-- Return jd, jdz from a Julian or Gregorian calendar date where
  -- jd = Julian date and its fractional part is zero at noon
--   jd = Julian date and its fractional part is zero at noon
  -- jdz = same, but assume time is 00:00:00 if no time given
--   jdz = same, but assume time is 00:00:00 if no time given
  -- http://www.tondering.dk/claus/cal/julperiod.php#formula
-- http://www.tondering.dk/claus/cal/julperiod.php#formula
  -- Testing shows this works for all dates from year -9999 to 9999!
-- Testing shows this works for all dates from year -9999 to 9999!
  -- JDN 0 is the 24-hour period starting at noon UTC on Monday
-- JDN 0 is the 24-hour period starting at noon UTC on Monday
  -- 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 offset
local offset
  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.calendar == 'Juliano' then
if date.calendar == 'Julian' then
    offset = floor(y/4) - 32083
offset = floor(y/4) - 32083
  else
else
    offset = floor(y/4) - floor(y/100) + floor(y/400) - 32045
offset = floor(y/4) - floor(y/100) + floor(y/400) - 32045
  end
end
  local m = date.month + 12*a - 3
local m = date.month + 12*a - 3
  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 + hms(date) - 0.5
jd = jd + hms(date) - 0.5
    return jd, jd
return jd, jd
  end
end
  return jd, jd - 0.5
return jd, jd - 0.5
end
end


local function set_date_from_jd(date)
local function set_date_from_jd(date)
  -- Set the fields of table date from its Julian date field.
-- Set the fields of table date from its Julian date field.
  -- Return true if date is valid.
-- Return true if date is valid.
  -- http://www.tondering.dk/claus/cal/julperiod.php#formula
-- http://www.tondering.dk/claus/cal/julperiod.php#formula
  -- 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.calendar
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 == 'Gregoriano' then
if calname == 'Gregorian' then
    low, high = -1930999.5, 5373484.49999
low, high = -1930999.5, 5373484.49999
  elseif calname == 'Juliano' then
elseif calname == 'Julian' then
    low, high = -1931076.5, 5373557.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 low <= jd and jd <= high) 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 -- 0 <= time < 1
local time = jd - jdn -- 0 <= time < 1
    if time >= 0.5 then -- if at or after midnight of next day
if time >= 0.5 then   -- if at or after midnight of next day
      jdn = jdn + 1
jdn = jdn + 1
      time = time - 0.5
time = time - 0.5
    else
else
      time = time + 0.5
time = time + 0.5
    end
end
    date.hour, date.minute, date.second = h_m_s(time)
date.hour, date.minute, date.second = h_m_s(time)
  else
else
    date.second = 0
date.second = 0
    date.minute = 0
date.minute = 0
    date.hour = 0
date.hour = 0
  end
end
  local b, c
local b, c
  if calname == 'Juliano' then
if calname == 'Julian' then
    b = 0
b = 0
    c = jdn + 32082
c = jdn + 32082
  else -- Gregorian
else -- Gregorian
    local a = jdn + 32044
local a = jdn + 32044
    b = floor((4*a + 3)/146097)
b = floor((4*a + 3)/146097)
    c = a - floor(146097*b/4)
c = a - floor(146097*b/4)
  end
end
  local d = floor((4*c + 3)/1461)
local d = floor((4*c + 3)/1461)
  local e = c - floor(1461*d/4)
local e = c - floor(1461*d/4)
  local m = floor((5*e + 2)/153)
local m = floor((5*e + 2)/153)
  date.day = e - floor((153*m + 2)/5) + 1
date.day = e - floor((153*m + 2)/5) + 1
  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)
  return true
return true
end
end


local function fix_numbers(numbers, y, m, d, H, M, S, partial, hastime, calendar)
local function fix_numbers(numbers, y, m, d, H, M, S, partial, hastime, calendar)
  -- Put the result of normalizing the given values in table numbers.
-- Put the result of normalizing the given values in table numbers.
  -- The result will have valid m, d values if y is valid; caller checks y.
-- The result will have valid m, d values if y is valid; caller checks y.
  -- The logic of PHP mktime is followed where m or d can be zero to mean
-- The logic of PHP mktime is followed where m or d can be zero to mean
  -- the previous unit, and -1 is the one before that, etc.
-- the previous unit, and -1 is the one before that, etc.
  -- Positive values carry forward.
-- Positive values carry forward.
  local date
local date
  if not (1 <= m and m <= 12) then
if not (1 <= m and m <= 12) then
    date = Date(y, 1, 1)
date = Date(y, 1, 1)
    if not date then return end
if not date then return end
    date = date + ((m - 1) .. 'm')
date = date + ((m - 1) .. 'm')
    y, m = date.year, date.month
y, m = date.year, date.month
  end
end
  local days_hms
local days_hms
  if not partial then
if not partial then
    if hastime and H and M and S then
if hastime and H and M and S then
      if not (0 <= H and H <= 23 and
if not (0 <= H and H <= 23 and
          0 <= M and M <= 59 and
0 <= M and M <= 59 and
          0 <= S and S <= 59) then
0 <= S and S <= 59) then
        days_hms = hms({ hour = H, minute = M, second = S })
days_hms = hms({ hour = H, minute = M, second = S })
      end
end
    end
end
    if days_hms or not (1 <= d and d <= days_in_month(y, m, calendar)) then
if days_hms or not (1 <= d and d <= days_in_month(y, m, calendar)) then
      date = date or Date(y, m, 1)
date = date or Date(y, m, 1)
      if not date then return end
if not date then return end
      date = date + (d - 1 + (days_hms or 0))
date = date + (d - 1 + (days_hms or 0))
      y, m, d = date.year, date.month, date.day
y, m, d = date.year, date.month, date.day
      if days_hms then
if days_hms then
        H, M, S = date.hour, date.minute, date.second
H, M, S = date.hour, date.minute, date.second
      end
end
    end
end
  end
end
  numbers.year = y
numbers.year = y
  numbers.month = m
numbers.month = m
  numbers.day = d
numbers.day = d
  if days_hms then
if days_hms then
    -- Don't set H unless it was valid because a valid H will set hastime.
-- Don't set H unless it was valid because a valid H will set hastime.
    numbers.hour = H
numbers.hour = H
    numbers.minute = M
numbers.minute = M
    numbers.second = S
numbers.second = S
  end
end
end
end


local function set_date_from_numbers(date, numbers, options)
local function set_date_from_numbers(date, numbers, options)
  -- Set the fields of table date from numeric values.
-- Set the fields of table date from numeric values.
  -- Return true if date is valid.
-- Return true if date is valid.
  if type(numbers) ~= 'table' then
if type(numbers) ~= 'table' then
    return
return
  end
end
  local y = numbers.year or date.year
local y = numbers.year   or date.year
  local m = numbers.month or date.month
local m = numbers.month or date.month
  local d = numbers.day or date.day
local d = numbers.day   or date.day
  local H = numbers.hour
local H = numbers.hour
  local M = numbers.minute or date.minute or 0
local M = numbers.minute or date.minute or 0
  local S = numbers.second or date.second or 0
local S = numbers.second or date.second or 0
  local need_fix
local need_fix
  if y and m and d then
if y and m and d then
    date.partial = nil
date.partial = nil
    if not (-9999 <= y and y <= 9999 and
if not (-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.calendar)) then
1 <= d and d <= days_in_month(y, m, date.calendar)) then
        if not date.want_fix then
if not date.want_fix then
          return
return
        end
end
        need_fix = true
need_fix = true
    end
end
  elseif y and date.partial then
elseif y and date.partial then
    if d or not (-9999 <= y and y <= 9999) then
if d or not (-9999 <= y and y <= 9999) then
      return
return
    end
end
    if m and not (1 <= m and m <= 12) then
if m and not (1 <= m and m <= 12) then
      if not date.want_fix then
if not date.want_fix then
        return
return
      end
end
      need_fix = true
need_fix = true
    end
end
  else
else
    return
return
  end
end
  if date.partial then
if date.partial then
    H = nil -- ignore any time
H = nil -- ignore any time
    M = nil
M = nil
    S = nil
S = nil
  else
else
    if H then
if H then
      -- It is not possible to set M or S without also setting H.
-- It is not possible to set M or S without also setting H.
      date.hastime = true
date.hastime = true
    else
else
      H = 0
H = 0
    end
end
    if not (0 <= H and H <= 23 and
if not (0 <= H and H <= 23 and
        0 <= M and M <= 59 and
0 <= M and M <= 59 and
        0 <= S and S <= 59) then
0 <= S and S <= 59) then
      if date.want_fix then
if date.want_fix then
        need_fix = true
need_fix = true
      else
else
        return
return
      end
end
    end
end
  end
end
  date.want_fix = nil
date.want_fix = nil
  if need_fix then
if need_fix then
    fix_numbers(numbers, y, m, d, H, M, S, date.partial, date.hastime, date.calendar)
fix_numbers(numbers, y, m, d, H, M, S, date.partial, date.hastime, date.calendar)
    return set_date_from_numbers(date, numbers, options)
return set_date_from_numbers(date, numbers, options)
  end
end
  date.year = y -- -9999 to 9999 ('n BC' → year = 1 - n)
date.year = y   -- -9999 to 9999 ('n BC' → year = 1 - n)
  date.month = m -- 1 to 12 (may be nil if partial)
date.month = m   -- 1 to 12 (may be nil if partial)
  date.day = d -- 1 to 31 (* = nil if partial)
date.day = d     -- 1 to 31 (* = nil if partial)
  date.hour = H -- 0 to 59 (*)
date.hour = H   -- 0 to 59 (*)
  date.minute = M -- 0 to 59 (*)
date.minute = M -- 0 to 59 (*)
  date.second = S -- 0 to 59 (*)
date.second = S -- 0 to 59 (*)
  if type(options) == 'table' then
if type(options) == 'table' then
    for _, k in ipairs({ 'am', 'era', 'format' }) do
for _, k in ipairs({ 'am', 'era', 'format' }) do
      if options[k] then
if options[k] then
        date.options[k] = options[k]
date.options[k] = options[k]
      end
end
    end
end
  end
end
  return true
return true
end
end


local function make_option_table(options1, options2)
local function make_option_table(options1, options2)
  -- 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 table 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.
-- 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.'
  -- 'pm', 'p.m.', 'PM', 'P.M.' (each has same meaning as corresponding item above)
--     '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,
  -- and am = 'pm' has the same meaning.
--   and am = 'pm' has the same meaning.
  -- Similarly, era = 'BC' means 'BC' is used if year <= 0.
-- 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.
  local result = { bydefault = {} }
local result = { bydefault = {} }
  if type(options1) == 'table' then
if type(options1) == 'table' then
    result.am = options1.am
result.am = options1.am
    result.era = options1.era
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'.
    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+)[:=](.+)$')
      if lhs then
if lhs then
        result[lhs] = rhs
result[lhs] = rhs
      end
end
    end
end
  end
end
  options2 = type(options2) == 'table' and options2 or {}
options2 = type(options2) == 'table' and options2 or {}
  local defaults = { am = 'am', era = 'BC' }
local defaults = { am = 'am', era = 'a. C.' }
  for k, v in pairs(defaults) do
for k, v in pairs(defaults) do
    if not result[k] then
if not result[k] then
      if options2[k] then
if options2[k] then
        result[k] = options2[k]
result[k] = options2[k]
      else
else
        result[k] = v
result[k] = v
        result.bydefault[k] = true
result.bydefault[k] = true
      end
end
    end
end
  end
end
  return result
return result
end
end


local ampm_options = {
local ampm_options = {
  -- lhs = input text accepted as an am/pm option
-- lhs = input text accepted as an am/pm option
  -- rhs = code used internally
-- rhs = code used internally
  ['am'] = 'am',
['am']   = 'am',
  ['AM'] = 'AM',
['AM']   = 'AM',
  ['a.m.'] = 'a.m.',
['a.m.'] = 'a.m.',
  ['A.M.'] = 'A.M.',
['A.M.'] = 'A.M.',
  ['pm'] = 'am', -- same as am
['pm']   = 'am', -- same as am
  ['PM'] = 'AM',
['PM']   = 'AM',
  ['p.m.'] = 'a.m.',
['p.m.'] = 'a.m.',
  ['P.M.'] = 'A.M.',
['P.M.'] = 'A.M.',
}
}


local era_text = {
local era_text = {
  -- Text for displaying an era with a positive year (after adjusting
-- Text for displaying an era with a positive year (after adjusting
  -- by replacing year with 1 - year if date.year <= 0).
-- by replacing year with 1 - year if date.year <= 0).
  -- options.era = { year<=0 , year>0 }
-- options.era = { year<=0 , year>0 }
  ['BCMINUS'] = { 'BC' , '' , isbc = true, sign = MINUS },
['BCMINUS']   = { 'BC'   , ''   , isbc = true, sign = MINUS },
  ['BCNEGATIVE'] = { 'BC' , '' , isbc = true, sign = '-' },
['BCNEGATIVE'] = { 'BC'   , ''   , isbc = true, sign = '-'   },
  ['BC'] = { 'BC' , '' , isbc = true },
['BC']         = { 'BC'   , ''   , isbc = true },
  ['B.C.'] = { 'B.C.' , '' , isbc = true },
['B.C.']       = { 'B.C.' , ''   , isbc = true },
  ['BCE'] = { 'BCE' , '' , isbc = true },
['BCE']       = { 'BCE'   , ''   , isbc = true },
  ['B.C.E.'] = { 'B.C.E.', '' , isbc = true },
['B.C.E.']     = { 'B.C.E.', ''   , isbc = true },
  ['AD'] = { 'BC' , 'AD' },
['AD']         = { 'BC'   , 'AD'   },
  ['A.D.'] = { 'B.C.' , 'A.D.' },
['A.D.']       = { 'B.C.' , 'A.D.' },
  ['CE'] = { 'BCE' , 'CE' },
['CE']         = { 'BCE'   , 'CE'   },
  ['C.E.'] = { 'B.C.E.', 'C.E.' },
['C.E.']       = { 'B.C.E.', 'C.E.' },
--ABREVIATURAS EN ESPAÑOL--
['a. C.']        = { 'a. C.'      , ''    , isbc = true },  --antes de Cristo
['a. de C.']      = { 'a. de C.'    , ''    , isbc = true },  --antes de Cristo
['a. de J. C.']  = { 'a. de J. C.' , ''    , isbc = true },  --antes de Jesucristo
['a. J. C.']      = { 'a. J. C.'    , ''    , isbc = true },  --antes de Jesucristo
['AEC']          = { 'AEC'        , ''    , isbc = true },  --antes de la era común
['a. e. c.']      = { 'a. e. c.'    , ''    , isbc = true },  --antes de la era común
['a. n. e.']      = { 'a. n. e.'    , ''    , isbc = true },  --antes de nuestra era
['a. e. v.']      = { 'a. e. v.'    , ''    , isbc = true },  --antes de la era vulgar
['d. C.']        = { 'a. C.'      , 'd. C.' },              --después de Cristo
['d. de C.']      = { 'a. de C.'    , 'd. de C.' },          --después de Cristo
['d. de J. C.']  = { 'a. de J. C.' , 'd. de J. C.' },        --después de Jesucristo
['d. J. C.']      = { 'a. J. C.'    , 'd. J. C.' },          --después de Jesucristo
['EC']            = { 'AEC'        , 'EC' },                --era común
['e. c.']        = { 'a. e. c.'    , 'e. c.' },              --era común
['n. e.']        = { 'a. n. e.'    , 'n. e.' },              --nuestra era
['e. v.']        = { 'a. e. v.'    , 'e. v.' },              --era vulgar
['A. D.']        = { 'a. C.'      , 'A. D.' },              --anno Domini
}
}


local function get_era_for_year(era, year)
local function get_era_for_year(era, year)
  return (era_text[era] or era_text['BC'])[year > 0 and 2 or 1] or ''
return (era_text[era] or era_text['a. C.'])[year > 0 and 2 or 1]:gsub(" ", "&nbsp;") or ''
end
end


local function strftime(date, format, options)
local function strftime(date, format, options)
  -- Return date formatted as a string using codes similar to those
-- Return date formatted as a string using codes similar to those
  -- in the C strftime library function.
-- in the C strftime library function.
  local sformat = string.format
local sformat = string.format
  local shortcuts = {
local shortcuts = {
    ['%c'] = '%-I:%M %p %-d %B %-Y %{era}', -- date and time: 2:30 pm 1 April 2016
['%c'] = '%-I:%M %p %-d de %B de %-Y %{era}', -- date and time: 2:30 pm 1 de abril de 2016
    ['%x'] = '%-d %B %-Y %{era}', -- date: 1 April 2016
['%C'] = '%-d de %B de %-Y, %H:%M:%S',   -- date and time: 1 de abril de 2016, 14:30
    ['%X'] = '%-I:%M %p', -- time: 2:30 pm
['%x'] = '%-d de %B de %-Y %{era}',           -- date:         1 de abril de 2016
  }
['%X'] = '%-I:%M %p',                         -- time:         2:30 pm
  if shortcuts[format] then
}
    format = shortcuts[format]
if shortcuts[format] then
  end
format = shortcuts[format]
  local codes = {
end
    a = { field = 'dayabbr' },
local codes = {
    A = { field = 'dayname' },
a = { field = 'dayabbr' },
    b = { field = 'monthabbr' },
A = { field = 'dayname' },
    B = { field = 'monthname' },
b = { field = 'monthabbr' },
    u = { fmt = '%d' , field = 'dowiso' },
B = { field = 'monthname' },
    w = { fmt = '%d' , field = 'dow' },
u = { fmt = '%d' , field = 'dowiso' },
    d = { fmt = '%02d', fmt2 = '%d', field = 'day' },
w = { fmt = '%d' , field = 'dow' },
    m = { fmt = '%02d', fmt2 = '%d', field = 'month' },
d = { fmt = '%02d', fmt2 = '%d', field = 'day' },
    Y = { fmt = '%04d', fmt2 = '%d', field = 'year' },
m = { fmt = '%02d', fmt2 = '%d', field = 'month' },
    H = { fmt = '%02d', fmt2 = '%d', field = 'hour' },
Y = { fmt = '%04d', fmt2 = '%d', field = 'year' },
    M = { fmt = '%02d', fmt2 = '%d', field = 'minute' },
H = { fmt = '%02d', fmt2 = '%d', field = 'hour' },
    S = { fmt = '%02d', fmt2 = '%d', field = 'second' },
M = { fmt = '%02d', fmt2 = '%d', field = 'minute' },
    j = { fmt = '%03d', fmt2 = '%d', field = 'dayofyear' },
S = { fmt = '%02d', fmt2 = '%d', field = 'second' },
    I = { fmt = '%02d', fmt2 = '%d', field = 'hour', special = 'hour12' },
j = { fmt = '%03d', fmt2 = '%d', field = 'dayofyear' },
    p = { field = 'hour', special = 'am' },
I = { fmt = '%02d', fmt2 = '%d', field = 'hour', special = 'hour12' },
  }
p = { field = 'hour', special = 'am' },
  options = make_option_table(options, date.options)
}
  local amopt = options.am
options = make_option_table(options, date.options)
  local eraopt = options.era
local amopt = options.am
  local function replace_code(spaces, modifier, id)
local eraopt = options.era
--print('spaces:' .. spaces .. ';')
local function replace_code(spaces, modifier, id)
--print('modifier:' .. modifier .. ';')
local code = codes[id]
--print('id:' .. id .. ';')
if code then
    local code = codes[id]
local fmt = code.fmt
    if code then
if modifier == '-' and code.fmt2 then
      local fmt = code.fmt
fmt = code.fmt2
      if modifier == '-' and code.fmt2 then
end
        fmt = code.fmt2
local value = date[code.field]
      end
if not value then
      local value = date[code.field]
return nil -- an undefined field in a partial date
      if not value then
end
        return nil -- an undefined field in a partial date
local special = code.special
      end
if special then
      local special = code.special
if special == 'hour12' then
      if special then
value = value % 12
        if special == 'hour12' then
value = value == 0 and 12 or value
          value = value % 12
elseif special == 'am' then
          value = value == 0 and 12 or value
local ap = ({
        elseif special == 'am' then
['a.m.'] = { 'a.m.', 'p.m.' },
          local ap = ({
['AM'] = { 'AM', 'PM' },
            ['a.m.'] = { 'a.m.', 'p.m.' },
['A.M.'] = { 'A.M.', 'P.M.' },
            ['AM'] = { 'AM', 'PM' },
})[ampm_options[amopt]] or { 'am', 'pm' }
            ['A.M.'] = { 'A.M.', 'P.M.' },
return (spaces == '' and '' or '&nbsp;') .. (value < 12 and ap[1] or ap[2])
          })[ampm_options[amopt]] or { 'am', 'pm' }
end
          return (spaces == '' and '' or '&nbsp;') .. (value < 12 and ap[1] or ap[2])
end
        end
if code.field == 'year' then
      end
local sign = (era_text[eraopt] or {}).sign
      if code.field == 'year' then
if not sign or format:find('%{era}', 1, true) then
        local sign = (era_text[eraopt] or {}).sign
sign = ''
        if not sign or format:find('%{era}', 1, true) then
if value <= 0 then
          sign = ''
value = 1 - value
          if value <= 0 then
end
            value = 1 - value
else
          end
if value >= 0 then
        else
sign = ''
          if value >= 0 then
else
            sign = ''
value = -value
          else
end
            value = -value
end
          end
return spaces .. sign .. sformat(fmt, value)
        end
end
        return spaces .. sign .. sformat(fmt, value)
return spaces .. (fmt and sformat(fmt, value) or value)
      end
end
if code.field == 'monthname' then
end
return spaces .. 'de ' .. (fmt and sformat(fmt, value) or value) .. ' de'
local function replace_property(spaces, id)
end
if id == 'era' then
      return spaces .. (fmt and sformat(fmt, value) or value)
-- Special case so can use local era option.
    end
local result = get_era_for_year(eraopt, date.year)
  end
if result == '' then
  local function replace_property(spaces, id)
return ''
    if id == 'era' then
end
      -- Special case so can use local era option.
return (spaces == '' and '' or '&nbsp;') .. result
      local result = get_era_for_year(eraopt, date.year)
end
      if result == '' then
local result = date[id]
        return ''
if type(result) == 'string' then
      end
return spaces .. result
      return (spaces == '' and '' or '&nbsp;') .. result
end
    end
if type(result) == 'number' then
    local result = date[id]
return spaces .. tostring(result)
    if type(result) == 'string' then
end
      return spaces .. result
if type(result) == 'boolean' then
    end
return spaces .. (result and '1' or '0')
    if type(result) == 'number' then
end
      return spaces .. tostring(result)
-- This occurs if id is an undefined field in a partial date, or is the name of a function.
    end
return nil
    if type(result) == 'boolean' then
end
      return spaces .. (result and '1' or '0')
local PERCENT = '\127PERCENT\127'
    end
return (format
    -- This occurs if id is an undefined field in a partial date, or is the name of a function.
:gsub('%%%%', PERCENT)
    return nil
:gsub('(%s*)%%{(%w+)}', replace_property)
  end
:gsub('(%s*)%%(%-?)(%a)', replace_code)
  local PERCENT = '\127PERCENT\127'
:gsub(PERCENT, '%%')
 
)
format = format
    :gsub('%%%%', PERCENT)
:gsub('(%s*)%%{(%w+)}', replace_property)
 
--print(format)
format = format
    :gsub('(%s*)%%(%-?)(%a)', replace_code)
--print(format)
 
format = format
    :gsub(PERCENT, '%%')
 
  return format
end
end


local function _date_text(date, fmt, options)
local function _date_text(date, fmt, options)
  -- Return a formatted string representing the given date.
-- Return a formatted string representing the given date.
  if not is_date(date) then
if not is_date(date) then
    error('date:text: need a date (use "date:text()" with a colon)', 2)
error('date:text: need a date (use "date:text()" with a colon)', 2)
  end
end
  if type(fmt) == 'string' and fmt:match('%S') then
if type(fmt) == 'string' and fmt:match('%S') then
    if fmt:find('%', 1, true) then
if fmt:find('%', 1, true) then
      return strftime(date, fmt, options)
return strftime(date, fmt, options)
    end
end
  elseif date.partial then
elseif date.partial then
    fmt = date.month and 'my' or 'y'
fmt = date.month and 'my' or 'y'
  else
else
    fmt = 'dmy'
fmt = 'dmy'
    if date.hastime then
if date.hastime then
      fmt = (date.second > 0 and 'hms ' or 'hm ') .. fmt
fmt = (date.second > 0 and 'hms ' or 'hm ') .. fmt
    end
end
  end
end
  local function bad_format()
local function bad_format()
    -- For consistency with other format processing, return given format
-- For consistency with other format processing, return given format
    -- (or cleaned format if original was not a string) if invalid.
-- (or cleaned format if original was not a string) if invalid.
    return mw.text.nowiki(fmt)
return mw.text.nowiki(fmt)
  end
end
  if date.partial then
if date.partial then
    -- Ignore days in standard formats like 'ymd'.
-- Ignore days in standard formats like 'ymd'.
    if fmt == 'ym' or fmt == 'ymd' then
if fmt == 'ym' or fmt == 'ymd' then
      fmt = date.month and '%Y-%m %{era}' or '%Y %{era}'
fmt = date.month and '%Y-%m %{era}' or '%Y %{era}'
    elseif fmt == 'my' or fmt == 'dmy' or fmt == 'mdy' then
elseif fmt == 'my' or fmt == 'dmy' or fmt == 'mdy' then
      fmt = date.month and '%B %-Y %{era}' or '%-Y %{era}'
fmt = date.month and '%B %-Y %{era}' or '%-Y %{era}'
    elseif fmt == 'y' then
elseif fmt == 'y' then
      fmt = date.month and '%-Y %{era}' or '%-Y %{era}'
fmt = date.month and '%-Y %{era}' or '%-Y %{era}'
    else
else
      return bad_format()
return bad_format()
    end
end
    return strftime(date, fmt, options)
return strftime(date, fmt, options)
  end
end
  local function hm_fmt()
local function hm_fmt()
    local plain = make_option_table(options, date.options).bydefault.am
local plain = make_option_table(options, date.options).bydefault.am
    return plain and '%H:%M' or '%-I:%M %p'
return plain and '%H:%M' or '%-I:%M %p'
  end
end
  local need_time = date.hastime
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 = hm_fmt()
f = hm_fmt()
      need_time = false
need_time = false
    elseif item == 'hms' then
elseif item == 'hms' then
      f = '%H:%M:%S'
f = '%H:%M:%S'
      need_time = false
need_time = false
    elseif item == 'ymd' then
elseif item == 'ymd' then
      f = '%Y-%m-%d %{era}'
f = '%Y-%m-%d %{era}'
    elseif item == 'mdy' then
elseif item == 'mdy' then
      f = '%B %-d, %-Y %{era}'
f = '%B %-d, %-Y %{era}'
    elseif item == 'dmy' then
elseif item == 'dmy' then
      f = '%-d %B %-Y %{era}'
f = '%-d de %B de %-Y %{era}'
    else
else
      return bad_format()
return bad_format()
    end
end
    t:add(f)
t:add(f)
  end
end
  fmt = t:join(' ')
fmt = t:join(' ')
  if need_time then
if need_time then
    fmt = hm_fmt() .. ' ' .. fmt
fmt = hm_fmt() .. ' ' .. fmt
  end
end
  return strftime(date, fmt, options)
return strftime(date, fmt, options)
end
end


local day_info = {
local day_info = {
  -- 0=Sun to 6=Sat
-- 0=Sun to 6=Sat
  [0] = { 'do', 'domingo' },
[0] = { 'do', 'domingo' },
  { 'lu', 'lunes' },
{ 'lu', 'lunes' },
  { 'ma', 'martes' },
{ 'ma', 'martes' },
  { 'mi', 'miércoles' },
{ 'mi', 'miércoles' },
  { 'ju', 'jueves' },
{ 'ju', 'jueves' },
  { 'vi', 'viernes' },
{ 'vi', 'viernes' },
  { 'sa', 'sábado' },
{ 'sa', 'sábado' },
}
}


local month_info = {
local month_info = {
  -- 1=Jan to 12=Dec
-- 1=Jan to 12=Dec
  { 'ene', 'enero' },
{ 'ene', 'enero' },
  { 'feb', 'febrero' },
{ 'feb', 'febrero' },
  { 'mar', 'marzo' },
{ 'mar', 'marzo' },
  { 'abr', 'abril' },
{ 'abr', 'abril' },
  { 'may', 'mayo' },
{ 'may', 'mayo' },
  { 'jun', 'junio' },
{ 'jun', 'junio' },
  { 'jul', 'julio' },
{ 'jul', 'julio' },
  { 'ago', 'agosto' },
{ 'ago', 'agosto' },
  { 'sep', 'septiembre' },
{ 'sep', 'septiembre' },
  { 'oct', 'octubre' },
{ 'oct', 'octubre' },
  { 'nov', 'noviembre' },
{ 'nov', 'noviembre' },
  { 'dic', 'diciembre' },
{ 'dic', 'diciembre' },
}
}
for k,v in pairs(month_info) do month_info[ v[1] ], month_info[ v[2] ] = v, v end


local function name_to_number(text, translate)
local function name_to_number(text, translate)
  if type(text) == 'string' then
if type(text) == 'string' then
    return translate['xx' .. text:lower():gsub('é', 'e'):gsub('á', 'a')]
return translate['xx' .. text:lower():gsub('é', 'e'):gsub('á', 'a')]
  end
end
end
end


local function day_number(text)
local function day_number(text)
  return name_to_number(text, {
return name_to_number(text, {
    xxdo = 0, xxdomingo = 0,
xxdo = 0, xxdomingo = 0,  
    xxlu = 1, xxlunes = 1,
xxlu = 1, xxlunes = 1, xxlune = 1,
    xxma = 2, xxmartes = 2,
xxma = 2, xxmartes = 2, xxmarte = 2,
    xxmi = 3, xxmiercoles = 3,
xxmi = 3, xxmiercoles = 3, xxmiercole = 3,
    xxju = 4, xxjueves = 4,
xxju = 4, xxjueves = 4, xxjueve = 4,
    xxvi = 5, xxviernes = 5,
xxvi = 5, xxviernes = 5, xxvierne = 5,
    xxsat = 6, xxsabado = 6
xxsat = 6, xxsabado = 6
  })
})
end
end


local function month_number(text)
local function month_number(text)
  return name_to_number(text, {
return name_to_number(text, {
    xxene = 1, xxenero = 1,
xxene = 1, xxenero = 1,
    xxfeb = 2, xxfebrero = 2,
xxfeb = 2, xxfebrero = 2,
    xxmar = 3, xxmarzo = 3,
xxmar = 3, xxmarzo = 3,
    xxabr = 4, xxabril = 4,
xxabr = 4, xxabril = 4,
    xxmay = 5, xxmayo = 5,
xxmay = 5, xxmayo = 5,
    xxjun = 6, xxjunio = 6,
xxjun = 6, xxjunio = 6,
    xxjul = 7, xxjulio = 7,
xxjul = 7, xxjulio = 7,
    xxago = 8, xxagosto = 8,
xxago = 8, xxagosto = 8,
    xxsep = 9, xxseptiembre = 9, xxsept = 9,
xxsep = 9, xxseptiembre = 9, xxsept = 9,
    xxoct = 10, xxoctubre = 10,
xxoct = 10, xxoctubre = 10,
    xxnov = 11, xxnoviembre = 11,
xxnov = 11, xxnoviembre = 11,
    xxdic = 12, xxdiciembre = 12,
xxdic = 12, xxdiciembre = 12,
  })
})
end
end


local function _list_text(list, fmt)
local function _list_text(list, fmt)
  -- Return a list of formatted strings from a list of dates.
-- Return a list of formatted strings from a list of dates.
  if not type(list) == 'table' then
if not type(list) == 'table' then
    error('date:list:text: need "list:text()" with a colon', 2)
error('date:list:text: need "list:text()" with a colon', 2)
  end
end
  local result = { join = _list_join }
local result = { join = _list_join }
  for i, date in ipairs(list) do
for i, date in ipairs(list) do
    result[i] = date:text(fmt)
result[i] = date:text(fmt)
  end
end
  return result
return result
end
end


local function _date_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.
  -- Dates in the list are in ascending order (oldest date first).
-- Dates in the list are in ascending order (oldest date first).
  -- The spec should be a string of form "<count> <day> <op>"
-- The spec should be a string of form "<count> <day> <op>"
  -- where each item is optional and
-- where each item is optional and
  -- count = number of items wanted in list
--   count = number of items wanted in list
  -- day = abbreviation or name such as Mon or Monday
--   day = abbreviation or name such as Mon or Monday
  -- op = >, >=, <, <= (default is > meaning after date)
--   op = >, >=, <, <= (default is > meaning after date)
  -- If no count is given, the list is for the specified days in date's month.
-- If no count is given, the list is for the specified days in date's month.
  -- The default day is date's day.
-- The default day is date's day.
  -- The spec can also be a positive or negative number:
-- The spec can also be a positive or negative number:
  -- -5 is equivalent to '5 <'
--   -5 is equivalent to '5 <'
  -- 5 is equivalent to '5' which is '5 >'
--   5 is equivalent to '5' which is '5 >'
  if not is_date(date) then
if not is_date(date) then
    error('date:list: need a date (use "date:list()" with a colon)', 2)
error('date:list: need a date (use "date:list()" with a colon)', 2)
  end
end
  local list = { text = _list_text }
local list = { text = _list_text }
  if date.partial then
if date.partial then
    return list
return list
  end
end
  local count, offset, operation
local count, offset, operation
  local ops = {
local ops = {
    ['>='] = { before = false, include = true },
['>='] = { before = false, include = true },
    ['>'] = { before = false, include = false },
['>'] = { before = false, include = false },
    ['<='] = { before = true , include = true },
['<='] = { before = true , include = true },
    ['<'] = { before = true , include = false },
['<'] = { before = true , include = false },
  }
}
  if spec then
if spec then
    if type(spec) == 'number' then
if type(spec) == 'number' then
      count = floor(spec + 0.5)
count = floor(spec + 0.5)
      if count < 0 then
if count < 0 then
        count = -count
count = -count
        operation = ops['<']
operation = ops['<']
      end
end
    elseif type(spec) == 'string' then
elseif type(spec) == 'string' then
      local num, day, op = spec:match('^%s*(%d*)%s*(%a*)%s*([<>=]*)%s*$')
local num, day, op = spec:match('^%s*(%d*)%s*(%a*)%s*([<>=]*)%s*$')
      if not num then
if not num then
        return list
return list
      end
end
      if num ~= '' then
if num ~= '' then
        count = tonumber(num)
count = tonumber(num)
      end
end
      if day ~= '' then
if day ~= '' then
        local dow = day_number(day:gsub('[sS]$', '')) -- accept plural days
local dow = day_number(day:gsub('[sS]$', '')) -- accept plural days
        if not dow then
if not dow then
          return list
return list
        end
end
        offset = dow - date.dow
offset = dow - date.dow
      end
end
      operation = ops[op]
operation = ops[op]
    else
else
      return list
return list
    end
end
  end
end
  offset = offset or 0
offset = offset or 0
  operation = operation or ops['>']
operation = operation or ops['>']
  local datefrom, dayfirst, daylast
local datefrom, dayfirst, daylast
  if operation.before then
if operation.before then
    if offset > 0 or (offset == 0 and not operation.include) then
if offset > 0 or (offset == 0 and not operation.include) then
      offset = offset - 7
offset = offset - 7
    end
end
    if count then
if count then
      if count > 1 then
if count > 1 then
        offset = offset - 7*(count - 1)
offset = offset - 7*(count - 1)
      end
end
      datefrom = date + offset
datefrom = date + offset
    else
else
      daylast = date.day + offset
daylast = date.day + offset
      dayfirst = daylast % 7
dayfirst = daylast % 7
      if dayfirst == 0 then
if dayfirst == 0 then
        dayfirst = 7
dayfirst = 7
      end
end
    end
end
  else
else
    if offset < 0 or (offset == 0 and not operation.include) then
if offset < 0 or (offset == 0 and not operation.include) then
      offset = offset + 7
offset = offset + 7
    end
end
    if count then
if count then
      datefrom = date + offset
datefrom = date + offset
    else
else
      dayfirst = date.day + offset
dayfirst = date.day + offset
      daylast = date.monthdays
daylast = date.monthdays
    end
end
  end
end
  if not count then
if not count then
    if daylast < dayfirst then
if daylast < dayfirst then
      return list
return list
    end
end
    count = floor((daylast - dayfirst)/7) + 1
count = floor((daylast - dayfirst)/7) + 1
    datefrom = Date(date, {day = dayfirst})
datefrom = Date(date, {day = dayfirst})
  end
end
  for i = 1, count do
for i = 1, count do
    if not datefrom then break end -- exceeds date limits
if not datefrom then break end -- exceeds date limits
    list[i] = datefrom
list[i] = datefrom
    datefrom = datefrom + 7
datefrom = datefrom + 7
  end
end
  return list
return list
end
end


-- A table to get the current date/time (UTC), but only if needed.
-- A table to get the current date/time (UTC), but only if needed.
local current = setmetatable({}, {
local current = setmetatable({}, {
  __index = function (self, key)
__index = function (self, key)
    local d = os.date('!*t')
local d = os.date('!*t')
    self.year = d.year
self.year = d.year
    self.month = d.month
self.month = d.month
    self.day = d.day
self.day = d.day
    self.hour = d.hour
self.hour = d.hour
    self.minute = d.min
self.minute = d.min
    self.second = d.sec
self.second = d.sec
    return rawget(self, key)
return rawget(self, key)
  end })
end })


local function extract_date(newdate, text)
local function extract_date(newdate, text)
  -- Parse the date/time in text and return n, o where
-- Parse the date/time in text and return n, o where
  -- n = table of numbers with date/time fields
--   n = table of numbers with date/time fields
  -- o = table of options for AM/PM or AD/BC or format, if any
--   o = table of options for AM/PM or AD/BC or format, if any
  -- or return nothing if date is known to be invalid.
-- or return nothing if date is known to be invalid.
  -- Caller determines if the values in n are valid.
-- Caller determines if the values in n are valid.
  -- A year must be positive ('1' to '9999'); use 'BC' for BC.
-- A year must be positive ('1' to '9999'); use 'BC' for BC.
  -- In a y-m-d string, the year must be four digits to avoid ambiguity
-- In a y-m-d string, the year must be four digits to avoid ambiguity
  -- ('0001' to '9999'). The only way to enter year <= 0 is by specifying
-- ('0001' to '9999'). The only way to enter year <= 0 is by specifying
  -- the date as three numeric parameters like ymd Date(-1, 1, 1).
-- the date as three numeric parameters like ymd Date(-1, 1, 1).
  -- Dates of form d/m/y, m/d/y, y/m/d are rejected as potentially ambiguous.
-- Dates of form d/m/y, m/d/y, y/m/d are [partially] rejected as potentially ambiguous.
  local date, options = {}, {}
local date, options = {}, {}
  if text:sub(-1) == 'Z' then
if text:sub(-1) == 'Z' then
    -- Extract date/time from a Wikidata timestamp.
-- Extract date/time from a Wikidata timestamp.
    -- The year can be 1 to 16 digits but this module handles 1 to 4 digits only.
-- The year can be 1 to 16 digits but this module handles 1 to 4 digits only.
    -- Examples: '+2016-06-21T14:30:00Z', '-0000000180-00-00T00:00:00Z'.
-- Examples: '+2016-06-21T14:30:00Z', '-0000000180-00-00T00:00:00Z'.
    local sign, y, m, d, H, M, S = text:match('^([+%-])(%d+)%-(%d%d)%-(%d%d)T(%d%d):(%d%d):(%d%d)Z$')
local sign, y, m, d, H, M, S = text:match('^([+%-])(%d+)%-(%d%d)%-(%d%d)T(%d%d):(%d%d):(%d%d)Z$')
    if sign then
if sign then
      y = tonumber(y)
y = tonumber(y)
      if sign == '-' and y > 0 then
if sign == '-' and y > 0 then
        y = -y
y = -y
      end
end
      if y <= 0 then
if y <= 0 then
        options.era = 'BCE'
options.era = 'a. C.' -- Sets the era when the year is negative on the timestamp
      end
end
      date.year = y
date.year = y
      m = tonumber(m)
m = tonumber(m)
      d = tonumber(d)
d = tonumber(d)
      H = tonumber(H)
H = tonumber(H)
      M = tonumber(M)
M = tonumber(M)
      S = tonumber(S)
S = tonumber(S)
      if m == 0 then
if m == 0 then
        newdate.partial = true
newdate.partial = true
        return date, options
return date, options
      end
end
      date.month = m
date.month = m
      if d == 0 then
if d == 0 then
        newdate.partial = true
newdate.partial = true
        return date, options
return date, options
      end
end
      date.day = d
date.day = d
      if H > 0 or M > 0 or S > 0 then
if H > 0 or M > 0 or S > 0 then
        date.hour = H
date.hour = H
        date.minute = M
date.minute = M
        date.second = S
date.second = S
      end
end
      return date, options
return date, options
    end
end
    return
return
  end
end
  local function extract_ymd(item)
 
    -- Called when no day or month has been set.
local a, b, c = text:match('^(%d+)[-./](%d+)[-./](%d+)$')
    local y, m, d = item:match('^(%d%d%d%d)%-(%w+)%-(%d%d?)$')
if a --[[ and b and c --]] then
    if y then
a = tonumber(a)
      if date.year then
b = tonumber(b)
        return
c = tonumber(c)
      end
--[[ -- use extract_ymd for this
      if m:match('^%d%d?$') then
if a > 31 and m <= 12 and c > 12 then
        m = tonumber(m)
date.year, date.month, date.day = a, b, c
      else
options.format = 'ymd'
        m = month_number(m)
newdate.partial = true
      end
return date, options
      if m then
else--]]
        date.year = tonumber(y)
if a > 12 and b <= 12 and c > 31 then
        date.month = m
date.year, date.month, date.day = c, b, a
        date.day = tonumber(d)
options.format = 'dmy'
        return true
newdate.partial = true
      end
return date, options
    end
elseif a <= 12 and b > 12 and c > 31 then
  end
date.year, date.month, date.day = c, a, b
  local function extract_day_or_year(item)
options.format = 'mdy'
    -- Called when a day would be valid, or
newdate.partial = true
    -- when a year would be valid if no year has been set and partial is set.
return date, options
    local number, suffix = item:match('^(%d%d?%d?%d?)(.*)$')
end
    if number then
end
      local n = tonumber(number)
 
      if #number <= 2 and n <= 31 then
local function extract_ymd(item)
        suffix = suffix:lower()
-- Called when no day or month has been set.
        if suffix == '' or suffix == 'st' or suffix == 'nd' or suffix == 'rd' or suffix == 'th' then
local y, m, d = item:match('^(%d%d%d%d)[-./](%w+)[-./](%d%d?)$')
          date.day = n
if y then
          return true
if date.year then
        end
return
      elseif suffix == '' and newdate.partial and not date.year then
end
        date.year = n
if m:match('^%d%d?$') then
        return true
m = tonumber(m)
      end
else
    end
m = month_number(m)
  end
end
  local function extract_month(item)
if m then
    -- A month must be given as a name or abbreviation; a number could be ambiguous.
date.year = tonumber(y)
    local m = month_number(item)
date.month = m
    if m then
date.day = tonumber(d)
      date.month = m
return true
      return true
end
    end
end
  end
end
  local function extract_time(item)
local function extract_day_or_year(item)
    local h, m, s = item:match('^(%d%d?):(%d%d)(:?%d*)$')
-- Called when a day would be valid, or
    if date.hour or not h then
-- when a year would be valid if no year has been set and partial is set.
      return
local number, suffix = item:match('^(%d%d?%d?%d?)(.*)$')
    end
if number then
    if s ~= '' then
local n = tonumber(number)
      s = s:match('^:(%d%d)$')
if #number <= 2 and n <= 31 then
      if not s then
suffix = suffix:lower()
        return
if suffix == '' or suffix == 'st' or suffix == 'nd' or suffix == 'rd' or suffix == 'th' then
      end
date.day = n
    end
return true
    date.hour = tonumber(h)
end
    date.minute = tonumber(m)
elseif suffix == '' and newdate.partial and not date.year then
    date.second = tonumber(s) -- nil if empty string
date.year = n
    return true
return true
  end
end
  local item_count = 0
end
  local index_time
end
  local function set_ampm(item)
local function extract_month(item)
    local H = date.hour
-- A month must be given as a name or abbreviation; a number could be ambiguous.
    if H and not options.am and index_time + 1 == item_count then
local m = month_number(item)
      options.am = ampm_options[item] -- caller checked this is not nil
if m then
      if item:match('^[Aa]') then
date.month = m
        if not (1 <= H and H <= 12) then
return true
          return
end
        end
end
        if H == 12 then
local function extract_time(item)
          date.hour = 0
local h, m, s = item:match('^(%d%d?):(%d%d)(:?%d*)$')
        end
if date.hour or not h then
      else
return
        if not (1 <= H and H <= 23) then
end
          return
if s ~= '' then
        end
s = s:match('^:(%d%d)$')
        if H <= 11 then
if not s then
          date.hour = H + 12
return
        end
end
      end
end
      return true
date.hour = tonumber(h)
    end
date.minute = tonumber(m)
  end
date.second = tonumber(s)  -- nil if empty string
  for item in text:gsub(',', ' '):gsub('de', ' '):gsub('&nbsp;', ' '):gmatch('%S+') do
return true
    item_count = item_count + 1
end
    if era_text[item] then
local item_count = 0
      -- Era is accepted in peculiar places.
local index_time
      if options.era then
local function set_ampm(item)
        return
local H = date.hour
      end
if H and not options.am and index_time + 1 == item_count then
      options.era = item
options.am = ampm_options[item]  -- caller checked this is not nil
    elseif ampm_options[item] then
if item:match('^[Aa]') then
      if not set_ampm(item) then
if not (1 <= H and H <= 12) then
        return
return
      end
end
    elseif item:find(':', 1, true) then
if H == 12 then
      if not extract_time(item) then
date.hour = 0
        return
end
      end
else
      index_time = item_count
if not (1 <= H and H <= 23) then
    elseif date.day and date.month then
return
      if date.year then
end
        return -- should be nothing more so item is invalid
if H <= 11 then
      end
date.hour = H + 12
      if not item:match('^(%d%d?%d?%d?)$') then
end
        return
end
      end
return true
      date.year = tonumber(item)
end
    elseif date.day then
end
      if not extract_month(item) then
    --Filtrar abreviaturas de era
        return
for key,value in pairs(era_text) do
      end
if string.find(text, key) ~= nil then
    elseif date.month then
options.era = key
      if not extract_day_or_year(item) then
text = string.gsub(text, key, '')
        return
break
      end
end
    elseif extract_month(item) then
end
      options.format = 'mdy'
for item in text:gsub(',', ' '):gsub('del?', ''):gsub('&nbsp;', ' '):gmatch('%S+') do
    elseif extract_ymd(item) then
item_count = item_count + 1
      options.format = 'ymd'
if era_text[item] then
    elseif extract_day_or_year(item) then
-- Era is accepted in peculiar places.
      if date.day then
if options.era then
        options.format = 'dmy'
return
      end
end
    else
options.era = item
      return
elseif ampm_options[item] then
    end
if not set_ampm(item) then
  end
return
  if not date.year or date.year == 0 then
end
    return
elseif item:find(':', 1, true) then
  end
if not extract_time(item) then
  local era = era_text[options.era]
return
  if era and era.isbc then
end
    date.year = 1 - date.year
index_time = item_count
  end
elseif date.day and date.month then
  return date, options
if date.year then
return  -- should be nothing more so item is invalid
end
if not item:match('^(%d%d?%d?%d?)$') then
return
end
date.year = tonumber(item)
elseif day_number(item) then
    --catch month day case
elseif date.day then
if not extract_month(item) then
return
end
elseif date.month then
if not extract_day_or_year(item) then
return
end
elseif extract_month(item) then
options.format = 'mdy'
elseif extract_ymd(item) then
options.format = 'ymd'
elseif extract_day_or_year(item) then
if date.day then
options.format = 'dmy'
end
else
--return  not return if item not recognized
end
end
if not date.year or date.year == 0 then
return
end
local era = era_text[options.era]
if era and era.isbc then
date.year = 1 - date.year
end
return date, options
end
end


local function autofill(date1, date2)
local function autofill(date1, date2)
  -- Fill any missing month or day in each date using the
-- Fill any missing month or day in each date using the
  -- corresponding component from the other date, if present,
-- corresponding component from the other date, if present,
  -- or with 1 if both dates are missing the month or day.
-- or with 1 if both dates are missing the month or day.
  -- This gives a good result for calculating the difference
-- This gives a good result for calculating the difference
  -- between two partial dates when no range is wanted.
-- between two partial dates when no range is wanted.
  -- Return filled date1, date2 (two full dates).
-- Return filled date1, date2 (two full dates).
  local function filled(a, b)
local function filled(a, b)
    local fillmonth, fillday
local fillmonth, fillday
    if not a.month then
if not a.month then
      fillmonth = b.month or 1
fillmonth = b.month or 1
    end
end
    if not a.day then
if not a.day then
      fillday = b.day or 1
fillday = b.day or 1
    end
end
    if fillmonth or fillday then -- need to create a new date
if fillmonth or fillday then -- need to create a new date
      if (fillmonth or a.month) == 2 and (fillday or a.day) == 29 then
if (fillmonth or a.month) == 2 and (fillday or a.day) == 29 then
        -- Avoid invalid date, for example with {{age|2013|29 Feb 2016}} or {{age|Feb 2013|29 Jan 2015}}.
-- Avoid invalid date, for example with {{age|2013|29 Feb 2016}} or {{age|Feb 2013|29 Jan 2015}}.
        if not is_leap_year(a.year, a.calendar) then
if not is_leap_year(a.year, a.calendar) then
          fillday = 28
fillday = 28
        end
end
      end
end
      a = Date(a, { month = fillmonth, day = fillday })
a = Date(a, { month = fillmonth, day = fillday })
    end
end
    return a
return a
  end
end
  return filled(date1, date2), filled(date2, date1)
return filled(date1, date2), filled(date2, date1)
end
end


local function date_add_sub(lhs, rhs, is_sub)
local function date_add_sub(lhs, rhs, is_sub)
  -- Return a new date from calculating (lhs + rhs) or (lhs - rhs),
-- Return a new date from calculating (lhs + rhs) or (lhs - rhs),
  -- or return nothing if invalid.
-- or return nothing if invalid.
  -- The result is nil if the calculated date exceeds allowable limits.
-- The result is nil if the calculated date exceeds allowable limits.
  -- Caller ensures that lhs is a date; its properties are copied for the new date.
-- Caller ensures that lhs is a date; its properties are copied for the new date.
  if lhs.partial then
if lhs.partial then
    -- Adding to a partial is not supported.
-- Adding to a partial is not supported.
    -- Can subtract a date or partial from a partial, but this is not called for that.
-- Can subtract a date or partial from a partial, but this is not called for that.
    return
return
  end
end
  local function is_prefix(text, word, minlen)
local function is_prefix(text, word, minlen)
    local n = #text
local n = #text
    return (minlen or 1) <= n and n <= #word and text == word:sub(1, n)
return (minlen or 1) <= n and n <= #word and text == word:sub(1, n)
  end
end
  local function do_days(n)
local function do_days(n)
    local forcetime, jd
local forcetime, jd
    if floor(n) == n then
if floor(n) == n then
      jd = lhs.jd
jd = lhs.jd
    else
else
      forcetime = not lhs.hastime
forcetime = not lhs.hastime
      jd = lhs.jdz
jd = lhs.jdz
    end
end
    jd = jd + (is_sub and -n or n)
jd = jd + (is_sub and -n or n)
    if forcetime then
if forcetime then
      jd = tostring(jd)
jd = tostring(jd)
      if not jd:find('.', 1, true) then
if not jd:find('.', 1, true) then
        jd = jd .. '.0'
jd = jd .. '.0'
      end
end
    end
end
    return Date(lhs, 'juliandate', jd)
return Date(lhs, 'juliandate', jd)
  end
end
  if type(rhs) == 'number' then
if type(rhs) == 'number' then
    -- Add/subtract days, including fractional days.
-- Add/subtract days, including fractional days.
    return do_days(rhs)
return do_days(rhs)
  end
end
  if type(rhs) == 'string' then
if type(rhs) == 'string' then
    -- rhs is a single component like '26m' or '26 months' (with optional sign).
-- rhs is a single component like '26m' or '26 months' (with optional sign).
    -- Fractions like '3.25d' are accepted for the units which are handled as days.
-- Fractions like '3.25d' are accepted for the units which are handled as days.
    local sign, numstr, id = rhs:match('^%s*([+-]?)([%d%.]+)%s*(%a+)$')
local sign, numstr, id = rhs:match('^%s*([+-]?)([%d%.]+)%s*(%a+)$')
    if sign then
if sign then
      if sign == '-' then
if sign == '-' then
        is_sub = not (is_sub and true or false)
is_sub = not (is_sub and true or false)
      end
end
      local y, m, days
local y, m, days
      local num = tonumber(numstr)
local num = tonumber(numstr)
      if not num then
if not num then
        return
return
      end
end
      id = id:lower()
id = id:lower()
      if is_prefix(id, 'years') then
if is_prefix(id, 'years') then
        y = num
y = num
        m = 0
m = 0
      elseif is_prefix(id, 'months') then
elseif is_prefix(id, 'months') then
        y = floor(num / 12)
y = floor(num / 12)
        m = num % 12
m = num % 12
      elseif is_prefix(id, 'weeks') then
elseif is_prefix(id, 'weeks') then
        days = num * 7
days = num * 7
      elseif is_prefix(id, 'days') then
elseif is_prefix(id, 'days') then
        days = num
days = num
      elseif is_prefix(id, 'hours') then
elseif is_prefix(id, 'hours') then
        days = num / 24
days = num / 24
      elseif is_prefix(id, 'minutes', 3) then
elseif is_prefix(id, 'minutes', 3) then
        days = num / (24 * 60)
days = num / (24 * 60)
      elseif is_prefix(id, 'seconds') then
elseif is_prefix(id, 'seconds') then
        days = num / (24 * 3600)
days = num / (24 * 3600)
      else
else
        return
return
      end
end
      if days then
if days then
        return do_days(days)
return do_days(days)
      end
end
      if numstr:find('.', 1, true) then
if numstr:find('.', 1, true) then
        return
return
      end
end
      if is_sub then
if is_sub then
        y = -y
y = -y
        m = -m
m = -m
      end
end
      assert(-11 <= m and m <= 11)
assert(-11 <= m and m <= 11)
      y = lhs.year + y
y = lhs.year + y
      m = lhs.month + m
m = lhs.month + m
      if m > 12 then
if m > 12 then
        y = y + 1
y = y + 1
        m = m - 12
m = m - 12
      elseif m < 1 then
elseif m < 1 then
        y = y - 1
y = y - 1
        m = m + 12
m = m + 12
      end
end
      local d = math.min(lhs.day, days_in_month(y, m, lhs.calendar))
local d = math.min(lhs.day, days_in_month(y, m, lhs.calendar))
      return Date(lhs, y, m, d)
return Date(lhs, y, m, d)
    end
end
  end
end
  if is_diff(rhs) then
if is_diff(rhs) then
    local days = rhs.age_days
local days = rhs.age_days
    if (is_sub or false) ~= (rhs.isnegative or false) then
if (is_sub or false) ~= (rhs.isnegative or false) then
      days = -days
days = -days
    end
end
    return lhs + days
return lhs + days
  end
end
end
end


local full_date_only = {
local full_date_only = {
  dayabbr = true,
dayabbr = true,
  dayname = true,
dayname = true,
  dow = true,
dow = true,
  dayofweek = true,
dayofweek = true,
  dowiso = true,
dowiso = true,
  dayofweekiso = true,
dayofweekiso = true,
  dayofyear = true,
dayofyear = true,
  gsd = true,
gsd = true,
  juliandate = true,
juliandate = true,
  jd = true,
jd = true,
  jdz = true,
jdz = true,
  jdnoon = true,
jdnoon = true,
}
}


-- Metatable for a date's calculated fields.
-- Metatable for a date's calculated fields.
local datemt = {
local datemt = {
  __index = function (self, key)
__index = function (self, key)
    if rawget(self, 'partial') then
if rawget(self, 'partial') then
      if full_date_only[key] then return end
if full_date_only[key] then return end
      if key == 'monthabbr' or key == 'monthdays' or key == 'monthname' then
if key == 'monthabbr' or key == 'monthdays' or key == 'monthname' then
        if not self.month then return end
if not self.month then return end
      end
end
    end
end
    local value
local value
    if key == 'dayabbr' then
if key == 'dayabbr' then
      value = day_info[self.dow][1]
value = day_info[self.dow][1]
    elseif key == 'dayname' then
elseif key == 'dayname' then
      value = day_info[self.dow][2]
value = day_info[self.dow][2]
    elseif key == 'dow' then
elseif key == 'dow' then
      value = (self.jdnoon + 1) % 7 -- day-of-week 0=Sun to 6=Sat
value = (self.jdnoon + 1) % 7 -- day-of-week 0=Sun to 6=Sat
    elseif key == 'dayofweek' then
elseif key == 'dayofweek' then
      value = self.dow
value = self.dow
    elseif key == 'dowiso' then
elseif key == 'dowiso' then
      value = (self.jdnoon % 7) + 1 -- ISO day-of-week 1=Mon to 7=Sun
value = (self.jdnoon % 7) + 1 -- ISO day-of-week 1=Mon to 7=Sun
    elseif key == 'dayofweekiso' then
elseif key == 'dayofweekiso' then
      value = self.dowiso
value = self.dowiso
    elseif key == 'dayofyear' then
elseif key == 'dayofyear' then
      local first = Date(self.year, 1, 1, self.calendar).jdnoon
local first = Date(self.year, 1, 1, self.calendar).jdnoon
      value = self.jdnoon - first + 1 -- day-of-year 1 to 366
value = self.jdnoon - first + 1 -- day-of-year 1 to 366
    elseif key == 'era' then
elseif key == 'era' then
      -- Era text (never a negative sign) from year and options.
-- Era text (never a negative sign) from year and options.
      value = get_era_for_year(self.options.era, self.year)
value = get_era_for_year(self.options.era, self.year)
    elseif key == 'format' then
elseif key == 'format' then
      value = self.options.format or 'dmy'
value = self.options.format or 'dmy'
    elseif key == 'gsd' then
elseif key == 'gsd' then
      -- GSD = 1 from 00:00:00 to 23:59:59 on 1 January 1 AD Gregorian calendar,
-- GSD = 1 from 00:00:00 to 23:59:59 on 1 January 1 AD Gregorian calendar,
      -- which is from jd 1721425.5 to 1721426.49999.
-- which is from jd 1721425.5 to 1721426.49999.
      value = floor(self.jd - 1721424.5)
value = floor(self.jd - 1721424.5)
    elseif key == 'juliandate' or key == 'jd' or key == 'jdz' then
elseif key == 'juliandate' or key == 'jd' or key == 'jdz' then
      local jd, jdz = julian_date(self)
local jd, jdz = julian_date(self)
      rawset(self, 'juliandate', jd)
rawset(self, 'juliandate', jd)
      rawset(self, 'jd', jd)
rawset(self, 'jd', jd)
      rawset(self, 'jdz', jdz)
rawset(self, 'jdz', jdz)
      return key == 'jdz' and jdz or jd
return key == 'jdz' and jdz or jd
    elseif key == 'jdnoon' then
elseif key == 'jdnoon' then
      -- Julian date at noon (an integer) on the calendar day when jd occurs.
-- Julian date at noon (an integer) on the calendar day when jd occurs.
      value = floor(self.jd + 0.5)
value = floor(self.jd + 0.5)
    elseif key == 'isleapyear' then
elseif key == 'isleapyear' then
      value = is_leap_year(self.year, self.calendar)
value = is_leap_year(self.year, self.calendar)
    elseif key == 'monthabbr' then
elseif key == 'monthabbr' then
      value = month_info[self.month][1]
value = month_info[self.month][1]
    elseif key == 'monthdays' then
elseif key == 'monthdays' then
      value = days_in_month(self.year, self.month, self.calendar)
value = days_in_month(self.year, self.month, self.calendar)
    elseif key == 'monthname' then
elseif key == 'monthname' then
      value = month_info[self.month][2]
value = month_info[self.month][2]
    end
end
    if value ~= nil then
if value ~= nil then
      rawset(self, key, value)
rawset(self, key, value)
      return value
return value
    end
end
  end,
end,
}
}


-- Date operators.
-- Date operators.
local function mt_date_add(lhs, rhs)
local function mt_date_add(lhs, rhs)
  if not is_date(lhs) then
if not is_date(lhs) then
    lhs, rhs = rhs, lhs -- put date on left (it must be a date for this to have been called)
lhs, rhs = rhs, lhs -- put date on left (it must be a date for this to have been called)
  end
end
  return date_add_sub(lhs, rhs)
return date_add_sub(lhs, rhs)
end
end


local function mt_date_sub(lhs, rhs)
local function mt_date_sub(lhs, rhs)
  if is_date(lhs) then
if is_date(lhs) then
    if is_date(rhs) then
if is_date(rhs) then
      return DateDiff(lhs, rhs)
return DateDiff(lhs, rhs)
    end
end
    return date_add_sub(lhs, rhs, true)
return date_add_sub(lhs, rhs, true)
  end
end
end
end


local function mt_date_concat(lhs, rhs)
local function mt_date_concat(lhs, rhs)
  return tostring(lhs) .. tostring(rhs)
return tostring(lhs) .. tostring(rhs)
end
end


local function mt_date_tostring(self)
local function mt_date_tostring(self)
  return self:text()
return self:text()
end
end


local function mt_date_eq(lhs, rhs)
local function mt_date_eq(lhs, rhs)
  -- Return true if dates identify same date/time where, for example,
-- Return true if dates identify same date/time where, for example,
  -- Date(-4712, 1, 1, 'Juliano') == Date(-4713, 11, 24, 'Gregoriano') is true.
-- Date(-4712, 1, 1, 'Julian') == Date(-4713, 11, 24, 'Gregorian') is true.
  -- This is called only if lhs and rhs have the same type and the same metamethod.
-- This is called only if lhs and rhs have the same type and the same metamethod.
  if lhs.partial or rhs.partial then
if lhs.partial or rhs.partial then
    -- One date is partial; the other is a partial or a full date.
-- One date is partial; the other is a partial or a full date.
    -- The months may both be nil, but must be the same.
-- The months may both be nil, but must be the same.
    return lhs.year == rhs.year and lhs.month == rhs.month and lhs.calendar == rhs.calendar
return lhs.year == rhs.year and lhs.month == rhs.month and lhs.calendar == rhs.calendar
  end
end
  return lhs.jdz == rhs.jdz
return lhs.jdz == rhs.jdz
end
end


local function mt_date_lt(lhs, rhs)
local function mt_date_lt(lhs, rhs)
  -- Return true if lhs < rhs, for example,
-- Return true if lhs < rhs, for example,
  -- Date('1 Jan 2016') < Date('06:00 1 Jan 2016') is true.
-- Date('1 Jan 2016') < Date('06:00 1 Jan 2016') is true.
  -- This is called only if lhs and rhs have the same type and the same metamethod.
-- This is called only if lhs and rhs have the same type and the same metamethod.
  if lhs.partial or rhs.partial then
if lhs.partial or rhs.partial then
    -- One date is partial; the other is a partial or a full date.
-- One date is partial; the other is a partial or a full date.
    if lhs.calendar ~= rhs.calendar then
if lhs.calendar ~= rhs.calendar then
      return lhs.calendar == 'Juliano'
return lhs.calendar == 'Julian'
    end
end
    if lhs.partial then
if lhs.partial then
      lhs = lhs.partial.first
lhs = lhs.partial.first
    end
end
    if rhs.partial then
if rhs.partial then
      rhs = rhs.partial.first
rhs = rhs.partial.first
    end
end
  end
end
  return lhs.jdz < rhs.jdz
return lhs.jdz < rhs.jdz
end
end


--[[ 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
Date('currentdate')
Date('currentdate')
Date('currentdatetime')
Date('currentdatetime')
Date('1 April 1995', 'julian') parse date from text
Date('1 April 1995', 'julian')     parse date from text
Date('1 April 1995 AD', 'julian') using an era sets a flag to do the same for output
Date('1 April 1995 AD', 'julian')   using an era sets a flag to do the same for output
Date('04:30:59 1 April 1995', 'julian')
Date('04:30:59 1 April 1995', 'julian')
Date(date) copy of an existing date
Date(date)                         copy of an existing date
Date(date, t) same, updated with y,m,d,H,M,S fields from table t
Date(date, t)                       same, updated with y,m,d,H,M,S fields from table t
Date(t)     date with y,m,d,H,M,S fields from table t
Date(t)                       date with y,m,d,H,M,S fields from table t
]]
]]
function Date(...) -- for forward declaration above
function Date(...) -- for forward declaration above
  -- Return a table holding a date assuming a uniform calendar always applies
-- Return a table holding a date assuming a uniform calendar always applies
  -- (proleptic Gregorian calendar or proleptic Julian calendar), or
-- (proleptic Gregorian calendar or proleptic Julian calendar), or
  -- return nothing if date is invalid.
-- return nothing if date is invalid.
  -- A partial date has a valid year, however its month may be nil, and
-- A partial date has a valid year, however its month may be nil, and
  -- its day and time fields are nil.
-- its day and time fields are nil.
  -- Field partial is set to false (if a full date) or a table (if a partial date).
-- Field partial is set to false (if a full date) or a table (if a partial date).
  local calendars = { julian = 'Juliano', gregorian = 'Gregoriano' }
local calendars = { julian = 'Julian', gregorian = 'Gregorian' }
  local newdate = {
local newdate = {
    _id = uniq,
_id = uniq,
    calendar = 'Gregoriano', -- default is Gregorian calendar
calendar = 'Gregorian', -- default is Gregorian calendar
    hastime = false, -- true if input sets a time
hastime = false, -- true if input sets a time
    hour = 0, -- always set hour/minute/second so don't have to handle nil
hour = 0, -- always set hour/minute/second so don't have to handle nil
    minute = 0,
minute = 0,
    second = 0,
second = 0,
    options = {},
options = {},
    list = _date_list,
list = _date_list,
    subtract = function (self, rhs, options)
subtract = function (self, rhs, options)
      return DateDiff(self, rhs, options)
return DateDiff(self, rhs, options)
    end,
end,
    text = _date_text,
text = _date_text,
  }
}
  local argtype, datetext, is_copy, jd_number, tnums
local argtype, datetext, is_copy, jd_number, tnums
  local numindex = 0
local numindex = 0
  local numfields = { 'year', 'month', 'day', 'hour', 'minute', 'second' }
local numfields = { 'year', 'month', 'day', 'hour', 'minute', 'second' }
  local numbers = {}
local numbers = {}
  for _, v in ipairs({...}) do
for _, v in ipairs({...}) do
    v = strip_to_nil(v)
v = strip_to_nil(v)
    local vlower = type(v) == 'string' and v:lower() or nil
local vlower = type(v) == 'string' and v:lower() or nil
    if v == nil then
if v == nil then
      -- Ignore empty arguments after stripping so modules can directly pass template parameters.
-- Ignore empty arguments after stripping so modules can directly pass template parameters.
    elseif calendars[vlower] then
elseif calendars[vlower] then
      newdate.calendar = calendars[vlower]
newdate.calendar = calendars[vlower]
    elseif vlower == 'partial' then
elseif vlower == 'partial' then
      newdate.partial = true
newdate.partial = true
    elseif vlower == 'fix' then
elseif vlower == 'fix' then
      newdate.want_fix = true
newdate.want_fix = true
    elseif is_date(v) then
elseif is_date(v) then
      -- Copy existing date (items can be overridden by other arguments).
-- Copy existing date (items can be overridden by other arguments).
      if is_copy or tnums then
if is_copy or tnums then
        return
return
      end
end
      is_copy = true
is_copy = true
      newdate.calendar = v.calendar
newdate.calendar = v.calendar
      newdate.partial = v.partial
newdate.partial = v.partial
      newdate.hastime = v.hastime
newdate.hastime = v.hastime
      newdate.options = v.options
newdate.options = v.options
      newdate.year = v.year
newdate.year = v.year
      newdate.month = v.month
newdate.month = v.month
      newdate.day = v.day
newdate.day = v.day
      newdate.hour = v.hour
newdate.hour = v.hour
      newdate.minute = v.minute
newdate.minute = v.minute
      newdate.second = v.second
newdate.second = v.second
    elseif type(v) == 'table' then
elseif type(v) == 'table' then
      if tnums then
if tnums then
        return
return
      end
end
      tnums = {}
tnums = {}
      local tfields = { year=1, month=1, day=1, hour=2, minute=2, second=2 }
local tfields = { year=1, month=1, day=1, hour=2, minute=2, second=2 }
      for tk, tv in pairs(v) do
for tk, tv in pairs(v) do
        if tfields[tk] then
if tfields[tk] then
          tnums[tk] = tonumber(tv)
tnums[tk] = tonumber(tv)
        end
end
        if tfields[tk] == 2 then
if tfields[tk] == 2 then
          newdate.hastime = true
newdate.hastime = true
        end
end
      end
end
    else
else
      local num = tonumber(v)
local num = tonumber(v)
      if not num and argtype == 'setdate' and numindex == 1 then
if not num and argtype == 'setdate' and numindex == 1 then
        num = month_number(v)
num = month_number(v)
      end
end
      if num then
if num then
        if not argtype then
if not argtype then
          argtype = 'setdate'
argtype = 'setdate'
        end
end
        if argtype == 'setdate' and numindex < 6 then
if argtype == 'setdate' and numindex < 6 then
          numindex = numindex + 1
numindex = numindex + 1
          numbers[numfields[numindex]] = num
numbers[numfields[numindex]] = num
        elseif argtype == 'juliandate' and not jd_number then
elseif argtype == 'juliandate' and not jd_number then
          jd_number = num
jd_number = num
          if type(v) == 'string' then
if type(v) == 'string' then
            if v:find('.', 1, true) then
if v:find('.', 1, true) then
              newdate.hastime = true
newdate.hastime = true
            end
end
          elseif num ~= floor(num) then
elseif num ~= floor(num) then
            -- The given value was a number. The time will be used
-- The given value was a number. The time will be used
            -- if the fractional part is nonzero.
-- if the fractional part is nonzero.
            newdate.hastime = true
newdate.hastime = true
          end
end
        else
else
          return
return
        end
end
      elseif argtype then
elseif argtype then
        return
return
      elseif type(v) == 'string' then
elseif type(v) == 'string' then
        if v == 'currentdate' or v == 'currentdatetime' or v == 'juliandate' then
if v == 'currentdate' or v == 'currentdatetime' or v == 'juliandate' then
          argtype = v
argtype = v
        else
else
          argtype = 'datetext'
argtype = 'datetext'
          datetext = v
datetext = v
        end
end
      else
else
        return
return
      end
end
    end
end
  end
end
  if argtype == 'datetext' then
if argtype == 'datetext' then
    if tnums or not set_date_from_numbers(newdate, extract_date(newdate, datetext)) then
if tnums or not set_date_from_numbers(newdate, extract_date(newdate, datetext)) then
      return
return
    end
end
  elseif argtype == 'juliandate' then
elseif argtype == 'juliandate' then
    newdate.partial = nil
newdate.partial = nil
    newdate.jd = jd_number
newdate.jd = jd_number
    if not set_date_from_jd(newdate) then
if not set_date_from_jd(newdate) then
      return
return
    end
end
  elseif argtype == 'currentdate' or argtype == 'currentdatetime' then
elseif argtype == 'currentdate' or argtype == 'currentdatetime' then
    newdate.partial = nil
newdate.partial = nil
    newdate.year = current.year
newdate.year = current.year
    newdate.month = current.month
newdate.month = current.month
    newdate.day = current.day
newdate.day = current.day
    if argtype == 'currentdatetime' then
if argtype == 'currentdatetime' then
      newdate.hour = current.hour
newdate.hour = current.hour
      newdate.minute = current.minute
newdate.minute = current.minute
      newdate.second = current.second
newdate.second = current.second
      newdate.hastime = true
newdate.hastime = true
    end
end
    newdate.calendar = 'Gregoriano' -- ignore any given calendar name
newdate.calendar = 'Gregorian' -- ignore any given calendar name
  elseif argtype == 'setdate' then
elseif argtype == 'setdate' then
    if tnums or not set_date_from_numbers(newdate, numbers) then
if tnums or not set_date_from_numbers(newdate, numbers) then
      return
return
    end
end
  elseif not (is_copy or tnums) then
elseif not (is_copy or tnums) then
    return
return
  end
end
  if tnums then
if tnums then
    newdate.jd = nil -- force recalculation in case jd was set before changes from tnums
newdate.jd = nil -- force recalculation in case jd was set before changes from tnums
    if not set_date_from_numbers(newdate, tnums) then
if not set_date_from_numbers(newdate, tnums) then
      return
return
    end
end
  end
end
  if newdate.partial then
if newdate.partial then
    local year = newdate.year
local year = newdate.year
    local month = newdate.month
local month = newdate.month
    local first = Date(year, month or 1, 1, newdate.calendar)
local first = Date(year, month or 1, 1, newdate.calendar)
    month = month or 12
month = month or 12
    local last = Date(year, month, days_in_month(year, month), newdate.calendar)
local last = Date(year, month, days_in_month(year, month), newdate.calendar)
    newdate.partial = { first = first, last = last }
newdate.partial = { first = first, last = last }
  else
else
    newdate.partial = false -- avoid index lookup
newdate.partial = false -- avoid index lookup
  end
end
  setmetatable(newdate, datemt)
setmetatable(newdate, datemt)
  local readonly = {}
local readonly = {}
  local mt = {
local mt = {
    __index = newdate,
__index = newdate,
    __newindex = function(t, k, v) error('date.' .. tostring(k) .. ' is read-only', 2) end,
__newindex = function(t, k, v) error('date.' .. tostring(k) .. ' is read-only', 2) end,
    __add = mt_date_add,
__add = mt_date_add,
    __sub = mt_date_sub,
__sub = mt_date_sub,
    __concat = mt_date_concat,
__concat = mt_date_concat,
    __tostring = mt_date_tostring,
__tostring = mt_date_tostring,
    __eq = mt_date_eq,
__eq = mt_date_eq,
    __lt = mt_date_lt,
__lt = mt_date_lt,
  }
}
  return setmetatable(readonly, mt)
return setmetatable(readonly, mt)
end
end


local function _diff_age(diff, code, options)
local function _diff_age(diff, code, options)
  -- Return a tuple of integer values from diff as specified by code, except that
-- Return a tuple of integer values from diff as specified by code, except that
  -- each integer may be a list of two integers for a diff with a partial date, or
-- each integer may be a list of two integers for a diff with a partial date, or
  -- return nil if the code is not supported.
-- return nil if the code is not supported.
  -- If want round, the least significant unit is rounded to nearest whole unit.
-- If want round, the least significant unit is rounded to nearest whole unit.
  -- For a duration, an extra day is added.
-- For a duration, an extra day is added.
  local wantround, wantduration, wantrange
local wantround, wantduration, wantrange
  if type(options) == 'table' then
if type(options) == 'table' then
    wantround = options.round
wantround = options.round
    wantduration = options.duration
wantduration = options.duration
    wantrange = options.range
wantrange = options.range
  else
else
    wantround = options
wantround = options
  end
end
  if not is_diff(diff) then
if not is_diff(diff) then
    local f = wantduration and 'duration' or 'age'
local f = wantduration and 'duration' or 'age'
    error(f .. ': need a date difference (use "diff:' .. f .. '()" with a colon)', 2)
error(f .. ': need a date difference (use "diff:' .. f .. '()" with a colon)', 2)
  end
end
  if diff.partial then
if diff.partial then
    -- Ignore wantround, wantduration.
-- Ignore wantround, wantduration.
    local function choose(v)
local function choose(v)
      if type(v) == 'table' then
if type(v) == 'table' then
        if not wantrange or v[1] == v[2] then
if not wantrange or v[1] == v[2] then
          -- Example: Date('partial', 2005) - Date('partial', 2001) gives
-- Example: Date('partial', 2005) - Date('partial', 2001) gives
          -- diff.years = { 3, 4 } to show the range of possible results.
-- diff.years = { 3, 4 } to show the range of possible results.
          -- If do not want a range, choose the second value as more expected.
-- If do not want a range, choose the second value as more expected.
          return v[2]
return v[2]
        end
end
      end
end
      return v
return v
    end
end
    if code == 'ym' or code == 'ymd' then
if code == 'ym' or code == 'ymd' then
      if not wantrange and diff.iszero then
if not wantrange and diff.iszero then
        -- This avoids an unexpected result such as
-- This avoids an unexpected result such as
        -- Date('partial', 2001) - Date('partial', 2001)
-- Date('partial', 2001) - Date('partial', 2001)
        -- giving diff = { years = 0, months = { 0, 11 } }
-- giving diff = { years = 0, months = { 0, 11 } }
        -- which would be reported as 0 years and 11 months.
-- which would be reported as 0 years and 11 months.
        return 0, 0
return 0, 0
      end
end
      return choose(diff.partial.years), choose(diff.partial.months)
return choose(diff.partial.years), choose(diff.partial.months)
    end
end
    if code == 'y' then
if code == 'y' then
      return choose(diff.partial.years)
return choose(diff.partial.years)
    end
end
    if code == 'm' or code == 'w' or code == 'd' then
if code == 'm' or code == 'w' or code == 'd' then
      return choose({ diff.partial.mindiff:age(code), diff.partial.maxdiff:age(code) })
return choose({ diff.partial.mindiff:age(code), diff.partial.maxdiff:age(code) })
    end
end
    return nil
return nil
  end
end
  local extra_days = wantduration and 1 or 0
local extra_days = wantduration and 1 or 0
  if code == 'wd' or code == 'w' or code == 'd' then
if code == 'wd' or code == 'w' or code == 'd' then
    local offset = wantround and 0.5 or 0
local offset = wantround and 0.5 or 0
    local days = diff.age_days + extra_days
local days = diff.age_days + extra_days
    if code == 'wd' or code == 'd' then
if code == 'wd' or code == 'd' then
      days = floor(days + offset)
days = floor(days + offset)
      if code == 'd' then
if code == 'd' then
        return days
return days
      end
end
      return floor(days/7), days % 7
return floor(days/7), days % 7
    end
end
    return floor(days/7 + offset)
return floor(days/7 + offset)
  end
end
  local H, M, S = diff.hours, diff.minutes, diff.seconds
local H, M, S = diff.hours, diff.minutes, diff.seconds
  if code == 'dh' or code == 'dhm' or code == 'dhms' or code == 'h' or code == 'hm' or code == 'hms' then
if code == 'dh' or code == 'dhm' or code == 'dhms' or code == 'h' or code == 'hm' or code == 'hms' then
    local days = floor(diff.age_days + extra_days)
local days = floor(diff.age_days + extra_days)
    local inc_hour
local inc_hour
    if wantround then
if wantround then
      if code == 'dh' or code == 'h' then
if code == 'dh' or code == 'h' then
        if M >= 30 then
if M >= 30 then
          inc_hour = true
inc_hour = true
        end
end
      elseif code == 'dhm' or code == 'hm' then
elseif code == 'dhm' or code == 'hm' then
        if S >= 30 then
if S >= 30 then
          M = M + 1
M = M + 1
          if M >= 60 then
if M >= 60 then
            M = 0
M = 0
            inc_hour = true
inc_hour = true
          end
end
        end
end
      else
else
        -- Nothing needed because S is an integer.
-- Nothing needed because S is an integer.
      end
end
      if inc_hour then
if inc_hour then
        H = H + 1
H = H + 1
        if H >= 24 then
if H >= 24 then
          H = 0
H = 0
          days = days + 1
days = days + 1
        end
end
      end
end
    end
end
    if code == 'dh' or code == 'dhm' or code == 'dhms' then
if code == 'dh' or code == 'dhm' or code == 'dhms' then
      if code == 'dh' then
if code == 'dh' then
        return days, H
return days, H
      elseif code == 'dhm' then
elseif code == 'dhm' then
        return days, H, M
return days, H, M
      else
else
        return days, H, M, S
return days, H, M, S
      end
end
    end
end
    local hours = days * 24 + H
local hours = days * 24 + H
    if code == 'h' then
if code == 'h' then
      return hours
return hours
    elseif code == 'hm' then
elseif code == 'hm' then
      return hours, M
return hours, M
    end
end
    return hours, M, S
return hours, M, S
  end
end
  if wantround then
if wantround then
    local inc_hour
local inc_hour
    if code == 'ymdh' or code == 'ymwdh' then
if code == 'ymdh' or code == 'ymwdh' then
      if M >= 30 then
if M >= 30 then
        inc_hour = true
inc_hour = true
      end
end
    elseif code == 'ymdhm' or code == 'ymwdhm' then
elseif code == 'ymdhm' or code == 'ymwdhm' then
      if S >= 30 then
if S >= 30 then
        M = M + 1
M = M + 1
        if M >= 60 then
if M >= 60 then
          M = 0
M = 0
          inc_hour = true
inc_hour = true
        end
end
      end
end
    elseif code == 'ymd' or code == 'ymwd' or code == 'yd' or code == 'md' then
elseif code == 'ymd' or code == 'ymwd' or code == 'yd' or code == 'md' then
      if H >= 12 then
if H >= 12 then
        extra_days = extra_days + 1
extra_days = extra_days + 1
      end
end
    end
end
    if inc_hour then
if inc_hour then
      H = H + 1
H = H + 1
      if H >= 24 then
if H >= 24 then
        H = 0
H = 0
        extra_days = extra_days + 1
extra_days = extra_days + 1
      end
end
    end
end
  end
end
  local y, m, d = diff.years, diff.months, diff.days
local y, m, d = diff.years, diff.months, diff.days
  if extra_days > 0 then
if extra_days > 0 then
    d = d + extra_days
d = d + extra_days
    if d > 28 or code == 'yd' then
if d > 28 or code == 'yd' then
      -- Recalculate in case have passed a month.
-- Recalculate in case have passed a month.
      diff = diff.date1 + extra_days - diff.date2
diff = diff.date1 + extra_days - diff.date2
      y, m, d = diff.years, diff.months, diff.days
y, m, d = diff.years, diff.months, diff.days
    end
end
  end
end
  if code == 'ymd' then
if code == 'ymd' then
    return y, m, d
return y, m, d
  elseif code == 'yd' then
elseif code == 'yd' then
    if y > 0 then
if y > 0 then
      -- It is known that diff.date1 > diff.date2.
-- It is known that diff.date1 > diff.date2.
      diff = diff.date1 - (diff.date2 + (y .. 'y'))
diff = diff.date1 - (diff.date2 + (y .. 'y'))
    end
end
    return y, floor(diff.age_days)
return y, floor(diff.age_days)
  elseif code == 'md' then
elseif code == 'md' then
    return y * 12 + m, d
return y * 12 + m, d
  elseif code == 'ym' or code == 'm' then
elseif code == 'ym' or code == 'm' then
    if wantround then
if wantround then
      if d >= 16 then
if d >= 16 then
        m = m + 1
m = m + 1
        if m >= 12 then
if m >= 12 then
          m = 0
m = 0
          y = y + 1
y = y + 1
        end
end
      end
end
    end
end
    if code == 'ym' then
if code == 'ym' then
      return y, m
return y, m
    end
end
    return y * 12 + m
return y * 12 + m
  elseif code == 'ymw' then
elseif code == 'ymw' then
    local weeks = floor(d/7)
local weeks = floor(d/7)
    if wantround then
if wantround then
      local days = d % 7
local days = d % 7
      if days > 3 or (days == 3 and H >= 12) then
if days > 3 or (days == 3 and H >= 12) then
        weeks = weeks + 1
weeks = weeks + 1
      end
end
    end
end
    return y, m, weeks
return y, m, weeks
  elseif code == 'ymwd' then
elseif code == 'ymwd' then
    return y, m, floor(d/7), d % 7
return y, m, floor(d/7), d % 7
  elseif code == 'ymdh' then
elseif code == 'ymdh' then
    return y, m, d, H
return y, m, d, H
  elseif code == 'ymwdh' then
elseif code == 'ymwdh' then
    return y, m, floor(d/7), d % 7, H
return y, m, floor(d/7), d % 7, H
  elseif code == 'ymdhm' then
elseif code == 'ymdhm' then
    return y, m, d, H, M
return y, m, d, H, M
  elseif code == 'ymwdhm' then
elseif code == 'ymwdhm' then
    return y, m, floor(d/7), d % 7, H, M
return y, m, floor(d/7), d % 7, H, M
  end
end
  if code == 'y' then
if code == 'y' then
    if wantround and m >= 6 then
if wantround and m >= 6 then
      y = y + 1
y = y + 1
    end
end
    return y
return y
  end
end
  return nil
return nil
end
end


local function _diff_duration(diff, code, options)
local function _diff_duration(diff, code, options)
  if type(options) ~= 'table' then
if type(options) ~= 'table' then
    options = { round = options }
options = { round = options }
  end
end
  options.duration = true
options.duration = true
  return _diff_age(diff, code, options)
return _diff_age(diff, code, options)
end
end


-- Metatable for some operations on date differences.
-- Metatable for some operations on date differences.
diffmt = { -- for forward declaration above
diffmt = { -- for forward declaration above
  __concat = function (lhs, rhs)
__concat = function (lhs, rhs)
    return tostring(lhs) .. tostring(rhs)
return tostring(lhs) .. tostring(rhs)
  end,
end,
  __tostring = function (self)
__tostring = function (self)
    return tostring(self.age_days)
return tostring(self.age_days)
  end,
end,
  __index = function (self, key)
__index = function (self, key)
    local value
local value
    if key == 'age_days' then
if key == 'age_days' then
      if rawget(self, 'partial') then
if rawget(self, 'partial') then
        local function jdz(date)
local function jdz(date)
          return (date.partial and date.partial.first or date).jdz
return (date.partial and date.partial.first or date).jdz
        end
end
        value = jdz(self.date1) - jdz(self.date2)
value = jdz(self.date1) - jdz(self.date2)
      else
else
        value = self.date1.jdz - self.date2.jdz
value = self.date1.jdz - self.date2.jdz
      end
end
    end
end
    if value ~= nil then
if value ~= nil then
      rawset(self, key, value)
rawset(self, key, value)
      return value
return value
     end
end
  end,
end,
}
 
function DateDiff(date1, date2, options)  -- for forward declaration above
-- Return a table with the difference between two dates (date1 - date2).
-- The difference is negative if date1 is older than date2.
-- Return nothing if invalid.
-- If d = date1 - date2 then
--    date1 = date2 + d
-- If date1 >= date2 and the dates have no H:M:S time specified then
--    date1 = date2 + (d.years..'y') + (d.months..'m') + d.days
-- where the larger time units are added first.
-- The result of Date(2015,1,x) + '1m' is Date(2015,2,28) for
-- x = 28, 29, 30, 31. That means, for example,
--     d = Date(2015,3,3) - Date(2015,1,31)
-- gives d.years, d.months, d.days = 0, 1, 3 (excluding date1).
if not (is_date(date1) and is_date(date2) and date1.calendar == date2.calendar) then
return
end
local wantfill
if type(options) == 'table' then
wantfill = options.fill
end
local isnegative = false
local iszero = false
if date1 < date2 then
isnegative = true
date1, date2 = date2, date1
elseif date1 == date2 then
iszero = true
end
-- It is known that date1 >= date2 (period is from date2 to date1).
if date1.partial or date2.partial then
-- Two partial dates might have timelines:
---------------------A=================B--- date1 is from A to B inclusive
--------C=======D-------------------------- date2 is from C to D inclusive
-- date1 > date2 iff A > C (date1.partial.first > date2.partial.first)
-- The periods can overlap ('April 2001' - '2001'):
-------------A===B------------------------- A=2001-04-01  B=2001-04-30
--------C=====================D------------ C=2001-01-01  D=2001-12-31
if wantfill then
date1, date2 = autofill(date1, date2)
else
local function zdiff(date1, date2)
local diff = date1 - date2
if diff.isnegative then
return date1 - date1  -- a valid diff in case we call its methods
end
return diff
end
local function getdate(date, which)
return date.partial and date.partial[which] or date
end
local maxdiff = zdiff(getdate(date1, 'last'), getdate(date2, 'first'))
local mindiff = zdiff(getdate(date1, 'first'), getdate(date2, 'last'))
local years, months
if maxdiff.years == mindiff.years then
years = maxdiff.years
if maxdiff.months == mindiff.months then
months = maxdiff.months
else
months = { mindiff.months, maxdiff.months }
end
else
years = { mindiff.years, maxdiff.years }
end
return setmetatable({
date1 = date1,
date2 = date2,
partial = {
years = years,
months = months,
maxdiff = maxdiff,
mindiff = mindiff,
},
isnegative = isnegative,
iszero = iszero,
age = _diff_age,
duration = _diff_duration,
}, diffmt)
end
end
local y1, m1 = date1.year, date1.month
local y2, m2 = date2.year, date2.month
local years = y1 - y2
local months = m1 - m2
local d1 = date1.day + hms(date1)
local d2 = date2.day + hms(date2)
local days, time
if d1 >= d2 then
days = d1 - d2
else
months = months - 1
-- Get days in previous month (before the "to" date) given December has 31 days.
local dpm = m1 > 1 and days_in_month(y1, m1 - 1, date1.calendar) or 31
if d2 >= dpm then
days = d1 - hms(date2)
else
days = dpm - d2 + d1
end
end
if months < 0 then
years = years - 1
months = months + 12
end
days, time = math.modf(days)
local H, M, S = h_m_s(time)
return setmetatable({
date1 = date1,
date2 = date2,
partial = false,  -- avoid index lookup
years = years,
months = months,
days = days,
hours = H,
minutes = M,
seconds = S,
isnegative = isnegative,
iszero = iszero,
age = _diff_age,
duration = _diff_duration,
}, diffmt)
end
 
 
local z = {
_current = current,
_Date = Date,
_days_in_month = days_in_month,
}
}


function DateDiff(date1, date2, options) -- for forward declaration above
 
  -- Return a table with the difference between two dates (date1 - date2).
-- Aqui comienzas las funciones adaptadas de [[Módulo:Fecha]] --
  -- The difference is negative if date1 is older than date2.
----------------------------------------------------------------
  -- Return nothing if invalid.
----------------------------------------------------------------
  -- If d = date1 - date2 then
----------------------------------------------------------------
  -- date1 = date2 + d
 
  -- If date1 >= date2 and the dates have no H:M:S time specified then
function z.fechaActual()
  -- date1 = date2 + (d.years..'y') + (d.months..'m') + d.days
local d = os.date('!*t')
  -- where the larger time units are added first.
  -- The result of Date(2015,1,x) + '1m' is Date(2015,2,28) for
local fecha = {}
  -- x = 28, 29, 30, 31. That means, for example,
fecha.anyo    = d.year
  -- d = Date(2015,3,3) - Date(2015,1,31)
fecha.mes    = d.month
  -- gives d.years, d.months, d.days = 0, 1, 3 (excluding date1).
fecha.dia    = d.day
  if not (is_date(date1) and is_date(date2) and date1.calendar == date2.calendar) then
fecha.hora    = d.hour
    return
fecha.minuto  = d.min
  end
fecha.segundo = d.sec
  local wantfill
  if type(options) == 'table' then
return fecha
    wantfill = options.fill
end
  end
 
  local isnegative = false
function validar(fecha)
  local iszero = false
fecha.anyo    = tonumber(fecha.anyo)
  if date1 < date2 then
fecha.mes    = tonumber(fecha.mes)
    isnegative = true
fecha.dia    = tonumber(fecha.dia)
    date1, date2 = date2, date1
fecha.hora    = tonumber(fecha.hora)
  elseif date1 == date2 then
fecha.minuto  = tonumber(fecha.minuto)
    iszero = true
fecha.segundo = tonumber(fecha.segundo)
  end
  -- It is known that date1 >= date2 (period is from date2 to date1).
-- Falta validar que es una fecha válida
  if date1.partial or date2.partial then
end
    -- Two partial dates might have timelines:
 
    ---------------------A=================B--- date1 is from A to B inclusive
function z.edad(fecha1, fecha2)
    --------C=======D-------------------------- date2 is from C to D inclusive
--Función que devuelve la edad en años entre dos fechas
    -- date1 > date2 iff A > C (date1.partial.first > date2.partial.first)
    -- The periods can overlap ('April 2001' - '2001'):
--Se supone que las fechas se han validado previamente.
    -------------A===B------------------------- A=2001-04-01 B=2001-04-30
    --------C=====================D------------ C=2001-01-01 D=2001-12-31
if not fecha1 then
     if wantfill then
return -- falta devolver un error
      date1, date2 = autofill(date1, date2)
end
if not fecha2 then
fecha2=z.fechaActual()
end
local anyos = fecha2.anyo - fecha1.anyo
--if true then return require('Módulo:Tablas').tostring(fecha2) end
if fecha2.mes < fecha1.mes or
  (fecha2.mes == fecha1.mes and fecha2.dia < fecha1.dia) then
  anyos = anyos - 1
end
if anyos < 0 then
return -- falta devolver error
elseif anyos == 0 then
return 'menos de un año'
elseif anyos == 1 then
return 'un año'
else
return anyos .. ' años'
end
end
 
function z.llamadaDesdeUnaPlantilla(frame)
function obtenerFecha(dia, mes, anyo)
local resultado={}
if dia then
resultado.dia  = dia
resultado.mes  = mes
resultado.anyo = anyo
validar(resultado)
return resultado
end
end
 
local args = frame.args
local funcion = z[args[1]]
local fecha1  = obtenerFecha(args[2], args[3], args[4])
local fecha2  = obtenerFecha(args[5], args[6], args[7])
 
return funcion(fecha1, fecha2)
end
     
 
-- Aqui comienzas las funciones adaptadas de [[Módulo:Fechas]] --
-----------------------------------------------------------------
-----------------------------------------------------------------
-----------------------------------------------------------------
 
 
function z.NombreDelMes(mes)
  -- Función que devuelve el nombre del mes, donde mes es un número entre 1 y 12.
  -- Si no es así se devuelve el valor de mes.
  -- Por ejemplo, 2  --> febrero
  --             02 --> febrero
  --             abril --> abril
  --             MAYO --> MAYO
  return month_info[tonumber(mes)][2] or mes
end
 
function z.Fecha(frame)
    -- Función que formatea una fecha
    -- El único parámetro obligatorio es el año o 3.
    -- Obtener los argumentos con los que se llama a la función
 
    local argumentos = {}
    local parent = {}
     if frame == mw.getCurrentFrame() then
        if  frame.args[3]  or frame.args["año"]  then
        argumentos = frame.args
        else
        parent = frame:getParent()
            argumentos = parent.args
        end
     else
     else
      local function zdiff(date1, date2)
         argumentos = frame
         local diff = date1 - date2
        if diff.isnegative then
          return date1 - date1 -- a valid diff in case we call its methods
        end
        return diff
      end
      local function getdate(date, which)
        return date.partial and date.partial[which] or date
      end
      local maxdiff = zdiff(getdate(date1, 'last'), getdate(date2, 'first'))
      local mindiff = zdiff(getdate(date1, 'first'), getdate(date2, 'last'))
      local years, months
      if maxdiff.years == mindiff.years then
        years = maxdiff.years
        if maxdiff.months == mindiff.months then
          months = maxdiff.months
        else
          months = { mindiff.months, maxdiff.months }
        end
      else
        years = { mindiff.years, maxdiff.years }
      end
      return setmetatable({
        date1 = date1,
        date2 = date2,
        partial = {
          years = years,
          months = months,
          maxdiff = maxdiff,
          mindiff = mindiff,
        },
        isnegative = isnegative,
        iszero = iszero,
        age = _diff_age,
        duration = _diff_duration,
      }, diffmt)
     end
     end
  end
  local y1, m1 = date1.year, date1.month
  local y2, m2 = date2.year, date2.month
  local years = y1 - y2
  local months = m1 - m2
  local d1 = date1.day + hms(date1)
  local d2 = date2.day + hms(date2)
  local days, time
  if d1 >= d2 then
    days = d1 - d2
  else
    months = months - 1
    -- Get days in previous month (before the "to" date) given December has 31 days.
    local dpm = m1 > 1 and days_in_month(y1, m1 - 1, date1.calendar) or 31
    if d2 >= dpm then
      days = d1 - hms(date2)
    else
      days = dpm - d2 + d1
    end
  end
  if months < 0 then
    years = years - 1
    months = months + 12
  end
  days, time = math.modf(days)
  local H, M, S = h_m_s(time)
  return setmetatable({
    date1 = date1,
    date2 = date2,
    partial = false, -- avoid index lookup
    years = years,
      
      
    local enlace = argumentos["enlace"] ~= "no"
    -- Obtener el día, el nombre del mes y el año incluyendo para los años negativos a.d.
    local dia = argumentos["día"] or argumentos[1] or ''   
    local mes = argumentos["mes"] or argumentos[2] or ''
    local anyo=tonumber(argumentos["año"] or argumentos[3]) or 0
    dia = (dia ~='') and (tonumber(dia) or dia) or dia -- Eliminar ceros a la izquierda del día.
    mes = (mes~='') and ((month_info[mes] and month_info[mes][2]) or month_info[tonumber(mes)][2] or mes) or mes -- Extraer nombre del mes
    anyo = (anyo<0) and (-anyo .. ' a. C.') or anyo
   
    local calendario = (argumentos["calendario"] == 'juliano') and ('<sup>[[Calendario juliano|jul.]]</sup>') or ''


months = months,
    -- Formatear la fecha dependiendo de si el día, el mes o el año están informados
    days = days,
local out = ''
    hours = H,
    minutes = M,
if anyo ~= 0 then
    seconds = S,
out = enlace and (out .. '[[' .. anyo .. ']]') or tostring(anyo)
    isnegative = isnegative,
if mes~='' then
    iszero = iszero,
if argumentos["mayúscula"] == 'sí' then
    age = _diff_age,
mes = mw.language.new('es'):ucfirst(mes)
     duration = _diff_duration,
end
  }, diffmt)
out = enlace and (mes .. ']] de ' .. out) or (mes .. ' de ' .. out)
end
if dia ~='' then
out = enlace and ('[[' .. dia .. ' de ' .. out .. calendario) or (dia .. ' de ' .. out .. calendario)
else
out = enlace and ('[[' .. out) or out
end
end
end
return out
end
function z.Numerica(frame)
local d = Date(frame.args[1])
     local err = '<strong class="error">Cadena de fecha no válida</strong>'
return (d == nil) and err or d:text('%Y%m%d')
end


return {
  _current = current,
return z
  _Date = Date,
  _days_in_month = days_in_month,
}

Revisión actual - 13:33 18 oct 2023

Nota: Éste módulo es una traducción de Module:Date lo que significa que cualquier módulo que dependa de Module:Date se puede utilizar con éste módulo sin ningún problema (siempre que el módulo en cuestión se adapte para entender fechas en español si fuese necesario). Es por eso que a pesar de que las fechas que devuelve al utilizar :text() son en español todos los nombres de los métodos internos y los parámetros se han mantenido en el idioma original.


Este módulo provee funciones relacionadas con el manejo de fechas para que puedan usarse en otros módulos. El módulo soporta fechas en el Calendario Gregoriano y en el calendario Juliano, desde el 9999 a. C. al 9999 d. C.. Los calendarios son prolépticos, es decir, se aplican incluso antes de su creación sin irregularidades.

Una fecha , con su hora opcional, se puede especificar en varios formatos diferentes y se puede formatear para su impresión usando otra variedad de formatos, por ejemplo '1 de abril de 2016' o el 'abril 1, 2016'. Las propiedades de la fecha incluyen su Fecha juliana y los días desde el 1 A.D. así como el día de la semana y el día del año.

También es posible comparar fechas (por ejemplo, fecha1 <= fecha2), y se les pueden aplicar operadores como la suma o la resta (por ejemplo, fecha + '3 meses' o fecha1 - fecha2). Estas operaciones funcionan con fechas de los calendarios Juliano y Gregoriano pero será nil si intentas operar con dos fechas de diferentes calendarios.

El módulo provee los siguientes objetos.

Export Descripción
_current Tabla con el año, mes, día, hora, minuto y segundo actuales.
_Date Función que devuelve una tabla para una fecha concreta.
_days_in_month Función que retorna el número de días en un mes.

En Módulo:Date/ejemplos se encuentran los ejemplos de uso del módulo y en Módulo discusión:Date/ejemplos está el resultado de su ejecución.

Formato de la salida

Es posible formatear la representación textual de la fecha.

local Date = require('Módulo:Date')._Date
local text = Date(2016, 7, 1):text()          -- devolvería '1 de julio de 2016'
local text = Date(2016, 7, 1):text('%-d de %B')  -- devolvería '1 de julio'
local text = Date('1 de julio de 2016'):text('mdy')  -- devolvería 'julio 1, 2016'

Los siguientes son los códigos de formato simplificados disponibles.

Código Resultado
hm horas:minutos, utilizando "am" o "pm" o una variante especificada (14:30 o 2:30 pm o lo especificado)
hms horas:minutos:segundos (14:30:45)
ymd año-mes-día (2016-07-01)
mdy mes día, año (julio 1, 2016)
dmy día de mes de año (1 de julio de 2016)

También están disponibles los siguientes códigos están disponibles (similar a los utilizados por strftime).

Código Resultado
%a Abreviación del día: Lu, Ma, ...
%A Nombre del día: lunes, martes, ...
%u Día de la semana: de 1 a 7 (Lunes a Domingo)
%w Día de la semana: de 0 a 6 (Domingo a Sábado)
%d Día del mes relleno con ceros: 01 a 31
%b Abreviación del mes: ene a dic
%B Nombre del mes: enero a diciembre
%m Mes relleno con ceros: 01 a 12
%Y Año relleno con ceros: 0012, 0120, 1200
%H Hora (reloj de 24 horas) rellena con ceros: 00 a 23
%I Hora (reloj de 12 horas) rellena con ceros: 01 a 12
%p AM o PM como con las opciones
%M Minutos rellenos con ceros: 00 a 59
%S Segundos rellenos con ceros: 00 a 59
%j Día del año relleno con ceros: 001 a 366
%-d Día del mes: 1 a 31
%-m Mes: 1 a 12
%-Y Año: 12, 120, 1200
%-H Hora: 0 a 23
%-M Minutos: 0 a 59
%-S Segundos: 0 a 59
%-j Día del año: 1 a 366
%-I Hora: 1 a 12
%% %

Además también se puede utilizar %{property} (donde property es una de las propiedades de la fecha).

Por ejemplo, una fecha como Date('1 de febrero de 2015 14:30:45 d. C.') tiene las siguientes propiedades.

Código Resultado
%{calendar} Gregorian
%{year} 2015
%{month} 2
%{day} 1
%{hour} 14
%{minute} 30
%{second} 45
%{dayabbr} do
%{dayname} Domingo
%{dayofweek} 0
%{dow} 0 (igual que 'dayofweek')
%{dayofweekiso} 7
%{dowiso} 7 (igual que 'dayofweekiso')
%{dayofyear} 32
%{era} d. C.
%{gsd} 735630 (números de días desde 1 de enero de 1 d. C.; el primero es el 1)
%{juliandate} 2457055.1046875 (Fecha juliana)
%{jd} 2457055.1046875 (igual que 'juliandate')
%{isleapyear} false
%{monthdays} 28
%{monthabbr} feb
%{monthname} febrero

También hay disponibles algunos atajos. Dada fecha = Date('1 feb 2015 14:30'), obtendremos los siguientes resultados.

Código Descripción Resultado del ejemplo Formato equivalente
fecha:text('%c') fecha y hora 2:30 pm 1 de febrero de 2015 %-I:%M %p %-d %B %-Y %{era}
fecha:text('%C') fecha y hora 1 de febrero de 2015, 14:30 %-d de %B de %-Y, %-H:%-M:%-S
fecha:text('%x') fecha 1 de febrero de 2015 %-d %B %-Y %{era}
fecha:text('%X') hora 2:30 pm %-I:%M %p

Fecha Juliana

El siguiente código contiene un ejemplo de la conversión de una Fecha juliana y la posterior obtención de información sobre esa fecha.

-- Código                                               -- Resultado
Date = require('Módulo:Date')._Date
fecha = Date('juliandate', 320)
número = fecha.gsd                                       -- -1721105
número = fecha.jd                                        -- 320
texto = fecha.dayname                                    -- sábado
texto = fecha:text()                                     -- 9 de octubre de 4713&nbsp;a. C.
texto = fecha:text('%Y-%m-%d')                           -- 4713-10-09
texto = fecha:text('%{era} %Y-%m-%d')                    -- a. C. 4713-10-09
texto = fecha:text('%Y-%m-%d %{era}')                    -- 4713-10-09&nbsp;BC
texto = fecha:text('%Y-%m-%d %{era}', 'era=B.C.E.')      -- 4713-10-09&nbsp;B.C.E.
texto = fecha:text('%Y-%m-%d', 'era=BCNEGATIVE')         -- -4712-10-09
texto = fecha:text('%Y-%m-%d', 'era=BCMINUS')            -- −4712-10-09 (utiliza el símbolo menos de Unicode U+2212)
texto = Date('juliandate',320):text('%{gsd} %{jd}')     -- -1721105 320
texto = Date('oct 9, 4713 B.C.E.'):text('%{gsd} %{jd}') -- -1721105 320
texto = Date(-4712,10,9):text('%{gsd} %{jd}')           -- -1721105 320

Diferencia de fechas

La diferencia entre dos fechas se puede determinar utilizando fecha1 - fecha2. El resultado es válido si las dos fechas utilizan el mismo calendario siendo 'nil' en otro caso. Es posible calcular una edad o una duración a partir de la diferencia entre dos fechas.

Por ejemplo:

-- Código                                    -- Resultado
Date = require('Módulo:Date')._Date
fecha1 = Date('21 mar 2015')
fecha2 = Date('4 dic 1999')
diff = fecha1 - fecha2
d = diff.age_days                            -- 5586
y, m, d = diff.years, diff.months, diff.days -- 15, 3, 17 (15 años + 3 meses + 17 días)
y, m, d = diff:age('ymd')                    -- 15, 3, 17
y, m, w, d = diff:age('ymwd')                -- 15, 3, 2, 3 (15 años + 3 meses + 2 semanas + 3 días)
y, m, w, d = diff:duration('ymwd')           -- 15, 3, 2, 4
d = diff:duration('d')                       -- 5587 (duración en días incluyendo el último día)

Una diferencia de fechas mantiene las fechas originales pero están cambiadas de tal forma que diff.date1 >= diff.date2 (siendo siempre diff.date1 la más reciente). Esto se muestra a continuación.

fecha1 = Date('21 mar 2015')
fecha2 = Date('4 dic 1999')
diff = fecha1 - fecha2
neg = diff.isnegative                        -- false
text = diff.date1:text()                     -- 21 de marzo de 2015
text = diff.date2:text()                     -- 4 de diciembre de 1999
diff = fecha2 - fecha1
neg = diff.isnegative                        -- true (se han cambiado las fechas de orden)
text = diff.date1:text()                     -- 21 de marzo de 2015
text = diff.date2:text()                     -- 4 de diciembre de 1999

Una diferencia de fechas también guarda la diferencia de tiempo:

fecha1 = Date('8 mar 2016 0:30:45')
fecha2 = Date('19 ene 2014 22:55')
diff = fecha1 - fecha2
y, m, d = diff.years, diff.months, diff.days      -- 2, 1, 17
H, M, S = diff.hours, diff.minutes, diff.seconds  -- 1, 35, 45

Una diferencia de fechas también se puede añadir o restar a una fecha.

fecha1 = Date('8 mar 2016 0:30:45')
fecha2 = Date('19 ene 2014 22:55')
diff = fecha1 - fecha2
fecha3 = fecha2 + diff
fecha4 = fecha1 - diff
texto = date3:text('ymd hms')        -- 2016-03-08 00:30:45
texto = date4:text('ymd hms')        -- 2014-01-19 22:55:00
igualdad = (fecha1 == fecha3)        -- true
igualdad = (fecha2 == fecha4)        -- true

Los métodos de edad 'age' y duración 'duration' aceptan un código que identifica los componentes a retornar. En el caso de la duración se incluye un día extra (el último).

Código Valores retornados
'ymwd' años, meses, semanas, días
'ymd' años, meses, días
'ym' años, meses
'y' años
'm' meses
'wd' semanas, días
'w' semanas
'd' días

Compatibilidad

Este módulo implementa las funciones de Módulo:Fecha y Módulo:Fechas, los tests de ambos módulos se encuentran funcionando contra este en Módulo:Date/tests y el resultado de su ejecución en Módulo discusión:Date/tests.


-- Date functions for use by other modules.
-- I18N and time zones are not supported.

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

local Date, DateDiff, diffmt  -- forward declarations
local uniq = { 'unique identifier' }

local function is_date(t)
	-- The system used to make a date read-only means there is no unique
	-- metatable that is conveniently accessible to check.
	return type(t) == 'table' and t._id == uniq
end

local function is_diff(t)
	return type(t) == 'table' and getmetatable(t) == diffmt
end

local function _list_join(list, sep)
	return table.concat(list, sep)
end

local function collection()
	-- Return a table to hold items.
	return {
		n = 0,
		add = function (self, item)
			self.n = self.n + 1
			self[self.n] = item
		end,
		join = _list_join,
	}
end

local function strip_to_nil(text)
	-- If text is a string, return its trimmed content, or nil if empty.
	-- Otherwise return text (convenient when Date fields are provided from
	-- another module which may pass a string, a number, or another type).
	if type(text) == 'string' then
		text = text:match('(%S.-)%s*$')
	end
	return text
end

local function is_leap_year(year, calname)
	-- Return true if year is a leap year.
	if calname == 'Julian' then
		return year % 4 == 0
	end
	return (year % 4 == 0 and year % 100 ~= 0) or year % 400 == 0
end

local function days_in_month(year, month, calname)
	-- Return number of days (1..31) in given month (1..12).
	if month == 2 and is_leap_year(year, calname) then
		return 29
	end
	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 from date's time, where (0 <= fraction < 1)
	-- if the values are valid, but could be anything if outside range.
	return (date.hour + (date.minute + date.second / 60) / 60) / 24
end

local function julian_date(date)
	-- Return jd, jdz from a Julian or Gregorian calendar date where
	--   jd = Julian date and its fractional part is zero at noon
	--   jdz = same, but assume time is 00:00:00 if no time given
	-- http://www.tondering.dk/claus/cal/julperiod.php#formula
	-- Testing shows this works for all dates from year -9999 to 9999!
	-- JDN 0 is the 24-hour period starting at noon UTC on Monday
	--    1 January 4713 BC  = (-4712, 1, 1)   Julian calendar
	--   24 November 4714 BC = (-4713, 11, 24) Gregorian calendar
	local offset
	local a = floor((14 - date.month)/12)
	local y = date.year + 4800 - a
	if date.calendar == 'Julian' then
		offset = floor(y/4) - 32083
	else
		offset = floor(y/4) - floor(y/100) + floor(y/400) - 32045
	end
	local m = date.month + 12*a - 3
	local jd = date.day + floor((153*m + 2)/5) + 365*y + offset
	if date.hastime then
		jd = jd + hms(date) - 0.5
		return jd, jd
	end
	return jd, jd - 0.5
end

local function set_date_from_jd(date)
	-- Set the fields of table date from its Julian date field.
	-- Return true if date is valid.
	-- http://www.tondering.dk/claus/cal/julperiod.php#formula
	-- This handles the proleptic Julian and Gregorian calendars.
	-- Negative Julian dates are not defined but they work.
	local calname = date.calendar
	local low, high  -- min/max limits for date ranges −9999-01-01 to 9999-12-31
	if calname == 'Gregorian' then
		low, high = -1930999.5, 5373484.49999
	elseif calname == 'Julian' then
		low, high = -1931076.5, 5373557.49999
	else
		return
	end
	local jd = date.jd
	if not (type(jd) == 'number' and low <= jd and jd <= high) then
		return
	end
	local jdn = floor(jd)
	if date.hastime then
		local time = jd - jdn  -- 0 <= time < 1
		if time >= 0.5 then    -- if at or after midnight of next day
			jdn = jdn + 1
			time = time - 0.5
		else
			time = time + 0.5
		end
		date.hour, date.minute, date.second = h_m_s(time)
	else
		date.second = 0
		date.minute = 0
		date.hour = 0
	end
	local b, c
	if calname == 'Julian' then
		b = 0
		c = jdn + 32082
	else  -- Gregorian
		local a = jdn + 32044
		b = floor((4*a + 3)/146097)
		c = a - floor(146097*b/4)
	end
	local d = floor((4*c + 3)/1461)
	local e = c - floor(1461*d/4)
	local m = floor((5*e + 2)/153)
	date.day = e - floor((153*m + 2)/5) + 1
	date.month = m + 3 - 12*floor(m/10)
	date.year = 100*b + d - 4800 + floor(m/10)
	return true
end

local function fix_numbers(numbers, y, m, d, H, M, S, partial, hastime, calendar)
	-- Put the result of normalizing the given values in table numbers.
	-- The result will have valid m, d values if y is valid; caller checks y.
	-- The logic of PHP mktime is followed where m or d can be zero to mean
	-- the previous unit, and -1 is the one before that, etc.
	-- Positive values carry forward.
	local date
	if not (1 <= m and m <= 12) then
		date = Date(y, 1, 1)
		if not date then return end
		date = date + ((m - 1) .. 'm')
		y, m = date.year, date.month
	end
	local days_hms
	if not partial then
		if hastime and H and M and S then
			if not (0 <= H and H <= 23 and
					0 <= M and M <= 59 and
					0 <= S and S <= 59) then
				days_hms = hms({ hour = H, minute = M, second = S })
			end
		end
		if days_hms or not (1 <= d and d <= days_in_month(y, m, calendar)) then
			date = date or Date(y, m, 1)
			if not date then return end
			date = date + (d - 1 + (days_hms or 0))
			y, m, d = date.year, date.month, date.day
			if days_hms then
				H, M, S = date.hour, date.minute, date.second
			end
		end
	end
	numbers.year = y
	numbers.month = m
	numbers.day = d
	if days_hms then
		-- Don't set H unless it was valid because a valid H will set hastime.
		numbers.hour = H
		numbers.minute = M
		numbers.second = S
	end
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.year   or date.year
	local m = numbers.month  or date.month
	local d = numbers.day    or date.day
	local H = numbers.hour
	local M = numbers.minute or date.minute or 0
	local S = numbers.second or date.second or 0
	local need_fix
	if y and m and d then
		date.partial = nil
		if not (-9999 <= y and y <= 9999 and
			1 <= m and m <= 12 and
			1 <= d and d <= days_in_month(y, m, date.calendar)) then
				if not date.want_fix then
					return
				end
				need_fix = true
		end
	elseif y and date.partial then
		if d or not (-9999 <= y and y <= 9999) then
			return
		end
		if m and not (1 <= m and m <= 12) then
			if not date.want_fix then
				return
			end
			need_fix = true
		end
	else
		return
	end
	if date.partial then
		H = nil  -- ignore any time
		M = nil
		S = nil
	else
		if H then
			-- It is not possible to set M or S without also setting H.
			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
			if date.want_fix then
				need_fix = true
			else
				return
			end
		end
	end
	date.want_fix = nil
	if need_fix then
		fix_numbers(numbers, y, m, d, H, M, S, date.partial, date.hastime, date.calendar)
		return set_date_from_numbers(date, numbers, options)
	end
	date.year = y    -- -9999 to 9999 ('n BC' → year = 1 - n)
	date.month = m   -- 1 to 12 (may be nil if partial)
	date.day = d     -- 1 to 31 (* = nil if partial)
	date.hour = H    -- 0 to 59 (*)
	date.minute = M  -- 0 to 59 (*)
	date.second = S  -- 0 to 59 (*)
	if type(options) == 'table' then
		for _, k in ipairs({ 'am', 'era', 'format' }) do
			if options[k] then
				date.options[k] = options[k]
			end
		end
	end
	return true
end

local function make_option_table(options1, options2)
	-- If options1 is a string, return a table with its settings, or
	-- if it is a table, use its settings.
	-- 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:
	-- 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.'
	-- Option am = 'am' does not mean the hour is AM; it means 'am' or 'pm' is used, depending on the hour,
	--    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}.
	-- BCNEGATIVE is similar but displays a hyphen.
	local result = { bydefault = {} }
	if type(options1) == 'table' then
		result.am = options1.am
		result.era = options1.era
	elseif type(options1) == 'string' then
		-- Example: 'am:AM era:BC' or 'am=AM era=BC'.
		for item in options1:gmatch('%S+') do
			local lhs, rhs = item:match('^(%w+)[:=](.+)$')
			if lhs then
				result[lhs] = rhs
			end
		end
	end
	options2 = type(options2) == 'table' and options2 or {}
	local defaults = { am = 'am', era = 'a. C.' }
	for k, v in pairs(defaults) do
		if not result[k] then
			if options2[k] then
				result[k] = options2[k]
			else
				result[k] = v
				result.bydefault[k] = true
			end
		end
	end
	return result
end

local ampm_options = {
	-- lhs = input text accepted as an am/pm option
	-- rhs = code used internally
	['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 era_text = {
	-- Text for displaying an era with a positive year (after adjusting
	-- by replacing year with 1 - year if date.year <= 0).
	-- options.era = { year<=0 , year>0 }
	['BCMINUS']    = { 'BC'    , ''    , isbc = true, sign = MINUS },
	['BCNEGATIVE'] = { 'BC'    , ''    , isbc = true, sign = '-'   },
	['BC']         = { 'BC'    , ''    , isbc = true },
	['B.C.']       = { 'B.C.'  , ''    , isbc = true },
	['BCE']        = { 'BCE'   , ''    , isbc = true },
	['B.C.E.']     = { 'B.C.E.', ''    , isbc = true },
	['AD']         = { 'BC'    , 'AD'   },
	['A.D.']       = { 'B.C.'  , 'A.D.' },
	['CE']         = { 'BCE'   , 'CE'   },
	['C.E.']       = { 'B.C.E.', 'C.E.' },
	--ABREVIATURAS EN ESPAÑOL--
	['a. C.']         = { 'a. C.'       , ''    , isbc = true },  --antes de Cristo
	['a. de C.']      = { 'a. de C.'    , ''    , isbc = true },  --antes de Cristo
	['a. de J. C.']   = { 'a. de J. C.' , ''    , isbc = true },  --antes de Jesucristo
	['a. J. C.']      = { 'a. J. C.'    , ''    , isbc = true },  --antes de Jesucristo
	['AEC']           = { 'AEC'         , ''    , isbc = true },  --antes de la era común
	['a. e. c.']      = { 'a. e. c.'    , ''    , isbc = true },  --antes de la era común
	['a. n. e.']      = { 'a. n. e.'    , ''    , isbc = true },  --antes de nuestra era
	['a. e. v.']      = { 'a. e. v.'    , ''    , isbc = true },  --antes de la era vulgar
	['d. C.']         = { 'a. C.'       , 'd. C.' },              --después de Cristo
	['d. de C.']      = { 'a. de C.'    , 'd. de C.' },           --después de Cristo
	['d. de J. C.']   = { 'a. de J. C.' , 'd. de J. C.' },        --después de Jesucristo
	['d. J. C.']      = { 'a. J. C.'    , 'd. J. C.' },           --después de Jesucristo
	['EC']            = { 'AEC'         , 'EC' },                 --era común
	['e. c.']         = { 'a. e. c.'    , 'e. c.' },              --era común
	['n. e.']         = { 'a. n. e.'    , 'n. e.' },              --nuestra era
	['e. v.']         = { 'a. e. v.'    , 'e. v.' },              --era vulgar
	['A. D.']         = { 'a. C.'       , 'A. D.' },              --anno Domini
}

local function get_era_for_year(era, year)
	return (era_text[era] or era_text['a. C.'])[year > 0 and 2 or 1]:gsub(" ", "&nbsp;") or ''
end

local function strftime(date, format, options)
	-- Return date formatted as a string using codes similar to those
	-- in the C strftime library function.
	local sformat = string.format
	local shortcuts = {
		['%c'] = '%-I:%M %p %-d de %B de %-Y %{era}',  -- date and time: 2:30 pm 1 de abril de 2016
		['%C'] = '%-d de %B de %-Y, %H:%M:%S',	   -- date and time: 1 de abril de 2016, 14:30
		['%x'] = '%-d de %B de %-Y %{era}',            -- date:          1 de abril de 2016
		['%X'] = '%-I:%M %p',                          -- time:          2:30 pm
	}
	if shortcuts[format] then
		format = shortcuts[format]
	end
	local codes = {
		a = { field = 'dayabbr' },
		A = { field = 'dayname' },
		b = { field = 'monthabbr' },
		B = { field = 'monthname' },
		u = { fmt = '%d'  , field = 'dowiso' },
		w = { fmt = '%d'  , field = 'dow' },
		d = { fmt = '%02d', fmt2 = '%d', field = 'day' },
		m = { fmt = '%02d', fmt2 = '%d', field = 'month' },
		Y = { fmt = '%04d', fmt2 = '%d', field = 'year' },
		H = { fmt = '%02d', fmt2 = '%d', field = 'hour' },
		M = { fmt = '%02d', fmt2 = '%d', field = 'minute' },
		S = { fmt = '%02d', fmt2 = '%d', field = 'second' },
		j = { fmt = '%03d', fmt2 = '%d', field = 'dayofyear' },
		I = { fmt = '%02d', fmt2 = '%d', field = 'hour', special = 'hour12' },
		p = { field = 'hour', special = 'am' },
	}
	options = make_option_table(options, date.options)
	local amopt = options.am
	local eraopt = options.era
	local function replace_code(spaces, modifier, id)
		local code = codes[id]
		if code then
			local fmt = code.fmt
			if modifier == '-' and code.fmt2 then
				fmt = code.fmt2
			end
			local value = date[code.field]
			if not value then
				return nil  -- an undefined field in a partial date
			end
			local special = code.special
			if special then
				if special == 'hour12' then
					value = value % 12
					value = value == 0 and 12 or value
				elseif special == 'am' then
					local ap = ({
						['a.m.'] = { 'a.m.', 'p.m.' },
						['AM'] = { 'AM', 'PM' },
						['A.M.'] = { 'A.M.', 'P.M.' },
					})[ampm_options[amopt]] or { 'am', 'pm' }
					return (spaces == '' and '' or '&nbsp;') .. (value < 12 and ap[1] or ap[2])
				end
			end
			if code.field == 'year' then
				local sign = (era_text[eraopt] or {}).sign
				if not sign or format:find('%{era}', 1, true) then
					sign = ''
					if value <= 0 then
						value = 1 - value
					end
				else
					if value >= 0 then
						sign = ''
					else
						value = -value
					end
				end
				return spaces .. sign .. sformat(fmt, value)
			end
			return spaces .. (fmt and sformat(fmt, value) or value)
		end
	end
	local function replace_property(spaces, id)
		if id == 'era' then
			-- Special case so can use local era option.
			local result = get_era_for_year(eraopt, date.year)
			if result == '' then
				return ''
			end
			return (spaces == '' and '' or '&nbsp;') .. result
		end
		local result = date[id]
		if type(result) == 'string' then
			return spaces .. result
		end
		if type(result) == 'number' then
			return  spaces .. tostring(result)
		end
		if type(result) == 'boolean' then
			return  spaces .. (result and '1' or '0')
		end
		-- This occurs if id is an undefined field in a partial date, or is the name of a function.
		return nil
	end
	local PERCENT = '\127PERCENT\127'
	return (format
		:gsub('%%%%', PERCENT)
		:gsub('(%s*)%%{(%w+)}', replace_property)
		:gsub('(%s*)%%(%-?)(%a)', replace_code)
		:gsub(PERCENT, '%%')
	)
end

local function _date_text(date, fmt, options)
	-- Return a formatted string representing the given date.
	if not is_date(date) then
		error('date:text: need a date (use "date:text()" with a colon)', 2)
	end
	if type(fmt) == 'string' and fmt:match('%S') then
		if fmt:find('%', 1, true) then
			return strftime(date, fmt, options)
		end
	elseif date.partial then
		fmt = date.month and 'my' or 'y'
	else
		fmt = 'dmy'
		if date.hastime then
			fmt = (date.second > 0 and 'hms ' or 'hm ') .. fmt
		end
	end
	local function bad_format()
		-- For consistency with other format processing, return given format
		-- (or cleaned format if original was not a string) if invalid.
		return mw.text.nowiki(fmt)
	end
	if date.partial then
		-- Ignore days in standard formats like 'ymd'.
		if fmt == 'ym' or fmt == 'ymd' then
			fmt = date.month and '%Y-%m %{era}' or '%Y %{era}'
		elseif fmt == 'my' or fmt == 'dmy' or fmt == 'mdy' then
			fmt = date.month and '%B %-Y %{era}' or '%-Y %{era}'
		elseif fmt == 'y' then
			fmt = date.month and '%-Y %{era}' or '%-Y %{era}'
		else
			return bad_format()
		end
		return strftime(date, fmt, options)
	end
	local function hm_fmt()
		local plain = make_option_table(options, date.options).bydefault.am
		return plain and '%H:%M' or '%-I:%M %p'
	end
	local need_time = date.hastime
	local t = collection()
	for item in fmt:gmatch('%S+') do
		local f
		if item == 'hm' then
			f = hm_fmt()
			need_time = false
		elseif item == 'hms' then
			f = '%H:%M:%S'
			need_time = false
		elseif item == 'ymd' then
			f = '%Y-%m-%d %{era}'
		elseif item == 'mdy' then
			f = '%B %-d, %-Y %{era}'
		elseif item == 'dmy' then
			f = '%-d de %B de %-Y %{era}'
		else
			return bad_format()
		end
		t:add(f)
	end
	fmt = t:join(' ')
	if need_time then
		fmt = hm_fmt() .. ' ' .. fmt
	end
	return strftime(date, fmt, options)
end

local day_info = {
	-- 0=Sun to 6=Sat
	[0] = { 'do', 'domingo' },
	{ 'lu', 'lunes' },
	{ 'ma', 'martes' },
	{ 'mi', 'miércoles' },
	{ 'ju', 'jueves' },
	{ 'vi', 'viernes' },
	{ 'sa', 'sábado' },
}

local month_info = {
	-- 1=Jan to 12=Dec
	{ 'ene', 'enero' },
	{ 'feb', 'febrero' },
	{ 'mar', 'marzo' },
	{ 'abr', 'abril' },
	{ 'may', 'mayo' },
	{ 'jun', 'junio' },
	{ 'jul', 'julio' },
	{ 'ago', 'agosto' },
	{ 'sep', 'septiembre' },
	{ 'oct', 'octubre' },
	{ 'nov', 'noviembre' },
	{ 'dic', 'diciembre' },
}
for k,v in pairs(month_info) do month_info[ v[1] ], month_info[ v[2] ] = v, v end

local function name_to_number(text, translate)
	if type(text) == 'string' then
		return translate['xx' .. text:lower():gsub('é', 'e'):gsub('á', 'a')]
	end
end

local function day_number(text)
	return name_to_number(text, {
		xxdo = 0, xxdomingo = 0, 
		xxlu = 1, xxlunes = 1, xxlune = 1,
		xxma = 2, xxmartes = 2, xxmarte = 2,
		xxmi = 3, xxmiercoles = 3, xxmiercole = 3,
		xxju = 4, xxjueves = 4, xxjueve = 4,
		xxvi = 5, xxviernes = 5, xxvierne = 5,
		xxsat = 6, xxsabado = 6
	})
end

local function month_number(text)
	return name_to_number(text, {
		xxene = 1, xxenero = 1,
		xxfeb = 2, xxfebrero = 2,
		xxmar = 3, xxmarzo = 3,
		xxabr = 4, xxabril = 4,
		xxmay = 5, xxmayo = 5,
		xxjun = 6, xxjunio = 6,
		xxjul = 7, xxjulio = 7,
		xxago = 8, xxagosto = 8,
		xxsep = 9, xxseptiembre = 9, xxsept = 9,
		xxoct = 10, xxoctubre = 10,
		xxnov = 11, xxnoviembre = 11,
		xxdic = 12, xxdiciembre = 12,
	})
end

local function _list_text(list, fmt)
	-- Return a list of formatted strings from a list of dates.
	if not type(list) == 'table' then
		error('date:list:text: need "list:text()" with a colon', 2)
	end
	local result = { join = _list_join }
	for i, date in ipairs(list) do
		result[i] = date:text(fmt)
	end
	return result
end

local function _date_list(date, spec)
	-- Return a possibly empty numbered table of dates meeting the specification.
	-- Dates in the list are in ascending order (oldest date first).
	-- 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 >'
	if not is_date(date) then
		error('date:list: need a date (use "date:list()" with a colon)', 2)
	end
	local list = { text = _list_text }
	if date.partial then
		return list
	end
	local count, offset, operation
	local ops = {
		['>='] = { before = false, include = true  },
		['>']  = { before = false, include = false },
		['<='] = { before = true , include = true  },
		['<']  = { before = true , include = false },
	}
	if spec then
		if type(spec) == 'number' then
			count = floor(spec + 0.5)
			if count < 0 then
				count = -count
				operation = ops['<']
			end
		elseif type(spec) == 'string' then
			local num, day, op = spec:match('^%s*(%d*)%s*(%a*)%s*([<>=]*)%s*$')
			if not num then
				return list
			end
			if num ~= '' then
				count = tonumber(num)
			end
			if day ~= '' then
				local dow = day_number(day:gsub('[sS]$', ''))  -- accept plural days
				if not dow then
					return list
				end
				offset = dow - date.dow
			end
			operation = ops[op]
		else
			return list
		end
	end
	offset = offset or 0
	operation = operation or ops['>']
	local datefrom, dayfirst, daylast
	if operation.before then
		if offset > 0 or (offset == 0 and not operation.include) then
			offset = offset - 7
		end
		if count then
			if count > 1 then
				offset = offset - 7*(count - 1)
			end
			datefrom = date + offset
		else
			daylast = date.day + offset
			dayfirst = daylast % 7
			if dayfirst == 0 then
				dayfirst = 7
			end
		end
	else
		if offset < 0 or (offset == 0 and not operation.include) then
			offset = offset + 7
		end
		if count then
			datefrom = date + offset
		else
			dayfirst = date.day + offset
			daylast = date.monthdays
		end
	end
	if not count then
		if daylast < dayfirst then
			return list
		end
		count = floor((daylast - dayfirst)/7) + 1
		datefrom = Date(date, {day = dayfirst})
	end
	for i = 1, count do
		if not datefrom then break end  -- exceeds date limits
		list[i] = datefrom
		datefrom = datefrom + 7
	end
	return list
end

-- A table to get the current date/time (UTC), but only if needed.
local current = setmetatable({}, {
	__index = function (self, key)
		local d = os.date('!*t')
		self.year = d.year
		self.month = d.month
		self.day = d.day
		self.hour = d.hour
		self.minute = d.min
		self.second = d.sec
		return rawget(self, key)
	end })

local function extract_date(newdate, 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 or format, if any
	-- or return nothing if date is known to be invalid.
	-- Caller determines if the values in n are valid.
	-- A year must be positive ('1' to '9999'); use 'BC' for BC.
	-- In a y-m-d string, the year must be four digits to avoid ambiguity
	-- ('0001' to '9999'). The only way to enter year <= 0 is by specifying
	-- the date as three numeric parameters like ymd Date(-1, 1, 1).
	-- Dates of form d/m/y, m/d/y, y/m/d are [partially] rejected as potentially ambiguous.
	local date, options = {}, {}
	if text:sub(-1) == 'Z' then
		-- Extract date/time from a Wikidata timestamp.
		-- The year can be 1 to 16 digits but this module handles 1 to 4 digits only.
		-- Examples: '+2016-06-21T14:30:00Z', '-0000000180-00-00T00:00:00Z'.
		local sign, y, m, d, H, M, S = text:match('^([+%-])(%d+)%-(%d%d)%-(%d%d)T(%d%d):(%d%d):(%d%d)Z$')
		if sign then
			y = tonumber(y)
			if sign == '-' and y > 0 then
				y = -y
			end
			if y <= 0 then
				options.era = 'a. C.' -- Sets the era when the year is negative on the timestamp
			end
			date.year = y
			m = tonumber(m)
			d = tonumber(d)
			H = tonumber(H)
			M = tonumber(M)
			S = tonumber(S)
			if m == 0 then
				newdate.partial = true
				return date, options
			end
			date.month = m
			if d == 0 then
				newdate.partial = true
				return date, options
			end
			date.day = d
			if H > 0 or M > 0 or S > 0 then
				date.hour = H
				date.minute = M
				date.second = S
			end
			return date, options
		end
		return
	end

	local a, b, c = text:match('^(%d+)[-./](%d+)[-./](%d+)$')
	if a --[[ and b and c --]] then
		a = tonumber(a)
		b = tonumber(b)
		c = tonumber(c)
		--[[ -- use extract_ymd for this
		if a > 31 and m <= 12 and c > 12 then
			date.year, date.month, date.day = a, b, c
			options.format = 'ymd'
			newdate.partial = true
			return date, options
		else--]]
		if a > 12 and b <= 12 and c > 31 then
			date.year, date.month, date.day = c, b, a
			options.format = 'dmy'
			newdate.partial = true
			return date, options
		elseif a <= 12 and b > 12 and c > 31 then
			date.year, date.month, date.day = c, a, b
			options.format = 'mdy'
			newdate.partial = true
			return date, options
		end
	end

	local function extract_ymd(item)
		-- Called when no day or month has been set.
		local y, m, d = item:match('^(%d%d%d%d)[-./](%w+)[-./](%d%d?)$')
		if y then
			if date.year then
				return
			end
			if m:match('^%d%d?$') then
				m = tonumber(m)
			else
				m = month_number(m)
			end
			if m then
				date.year = tonumber(y)
				date.month = m
				date.day = tonumber(d)
				return true
			end
		end
	end
	local function extract_day_or_year(item)
		-- Called when a day would be valid, or
		-- when a year would be valid if no year has been set and partial is set.
		local number, suffix = item:match('^(%d%d?%d?%d?)(.*)$')
		if number then
			local n = tonumber(number)
			if #number <= 2 and n <= 31 then
				suffix = suffix:lower()
				if suffix == '' or suffix == 'st' or suffix == 'nd' or suffix == 'rd' or suffix == 'th' then
					date.day = n
					return true
				end
			elseif suffix == '' and newdate.partial and not date.year then
				date.year = n
				return true
			end
		end
	end
	local function extract_month(item)
		-- A month must be given as a name or abbreviation; a number could be ambiguous.
		local m = month_number(item)
		if m then
			date.month = m
			return true
		end
	end
	local function extract_time(item)
		local h, m, s = item:match('^(%d%d?):(%d%d)(:?%d*)$')
		if date.hour or not h then
			return
		end
		if s ~= '' then
			s = s:match('^:(%d%d)$')
			if not s then
				return
			end
		end
		date.hour = tonumber(h)
		date.minute = tonumber(m)
		date.second = tonumber(s)  -- nil if empty string
		return true
	end
	local item_count = 0
	local index_time
	local function set_ampm(item)
		local H = date.hour
		if H and not options.am and index_time + 1 == item_count then
			options.am = ampm_options[item]  -- caller checked this is not nil
			if item:match('^[Aa]') then
				if not (1 <= H and H <= 12) then
					return
				end
				if H == 12 then
					date.hour = 0
				end
			else
				if not (1 <= H and H <= 23) then
					return
				end
				if H <= 11 then
					date.hour = H + 12
				end
			end
			return true
		end
	end
    --Filtrar abreviaturas de era
	for key,value in pairs(era_text) do
		if string.find(text, key) ~= nil then
			options.era = key
			text = string.gsub(text, key, '')
			break
		end
	end
	for item in text:gsub(',', ' '):gsub('del?', ''):gsub('&nbsp;', ' '):gmatch('%S+') do
		item_count = item_count + 1
		if era_text[item] then
			-- Era is accepted in peculiar places.
			if options.era then
				return
			end
			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.day and date.month then
			if date.year then
				return  -- should be nothing more so item is invalid
			end
			if not item:match('^(%d%d?%d?%d?)$') then
				return
			end
			date.year = tonumber(item)
		elseif day_number(item) then
		    --catch month day case
		elseif date.day then
			if not extract_month(item) then
				return
			end
		elseif date.month then
			if not extract_day_or_year(item) then
				return
			end
		elseif extract_month(item) then
			options.format = 'mdy'
		elseif extract_ymd(item) then
			options.format = 'ymd'
		elseif extract_day_or_year(item) then
			if date.day then
				options.format = 'dmy'
			end
		else
			--return  not return if item not recognized
		end
	end
	if not date.year or date.year == 0 then
		return
	end
	local era = era_text[options.era]
	if era and era.isbc then
		date.year = 1 - date.year
	end
	return date, options
end

local function autofill(date1, date2)
	-- Fill any missing month or day in each date using the
	-- corresponding component from the other date, if present,
	-- or with 1 if both dates are missing the month or day.
	-- This gives a good result for calculating the difference
	-- between two partial dates when no range is wanted.
	-- Return filled date1, date2 (two full dates).
	local function filled(a, b)
		local fillmonth, fillday
		if not a.month then
			fillmonth = b.month or 1
		end
		if not a.day then
			fillday = b.day or 1
		end
		if fillmonth or fillday then  -- need to create a new date
			if (fillmonth or a.month) == 2 and (fillday or a.day) == 29 then
				-- Avoid invalid date, for example with {{age|2013|29 Feb 2016}} or {{age|Feb 2013|29 Jan 2015}}.
				if not is_leap_year(a.year, a.calendar) then
					fillday = 28
				end
			end
			a = Date(a, { month = fillmonth, day = fillday })
		end
		return a
	end
	return filled(date1, date2), filled(date2, date1)
end

local function date_add_sub(lhs, rhs, is_sub)
	-- Return a new date from calculating (lhs + rhs) or (lhs - rhs),
	-- or return nothing if invalid.
	-- The result is nil if the calculated date exceeds allowable limits.
	-- Caller ensures that lhs is a date; its properties are copied for the new date.
	if lhs.partial then
		-- Adding to a partial is not supported.
		-- Can subtract a date or partial from a partial, but this is not called for that.
		return
	end
	local function is_prefix(text, word, minlen)
		local n = #text
		return (minlen or 1) <= n and n <= #word and text == word:sub(1, n)
	end
	local function do_days(n)
		local forcetime, jd
		if floor(n) == n then
			jd = lhs.jd
		else
			forcetime = not lhs.hastime
			jd = lhs.jdz
		end
		jd = jd + (is_sub and -n or n)
		if forcetime then
			jd = tostring(jd)
			if not jd:find('.', 1, true) then
				jd = jd .. '.0'
			end
		end
		return Date(lhs, 'juliandate', jd)
	end
	if type(rhs) == 'number' then
		-- Add/subtract days, including fractional days.
		return do_days(rhs)
	end
	if type(rhs) == 'string' then
		-- rhs is a single component like '26m' or '26 months' (with optional sign).
		-- Fractions like '3.25d' are accepted for the units which are handled as days.
		local sign, numstr, id = rhs:match('^%s*([+-]?)([%d%.]+)%s*(%a+)$')
		if sign then
			if sign == '-' then
				is_sub = not (is_sub and true or false)
			end
			local y, m, days
			local num = tonumber(numstr)
			if not num then
				return
			end
			id = id:lower()
			if is_prefix(id, 'years') then
				y = num
				m = 0
			elseif is_prefix(id, 'months') then
				y = floor(num / 12)
				m = num % 12
			elseif is_prefix(id, 'weeks') then
				days = num * 7
			elseif is_prefix(id, 'days') then
				days = num
			elseif is_prefix(id, 'hours') then
				days = num / 24
			elseif is_prefix(id, 'minutes', 3) then
				days = num / (24 * 60)
			elseif is_prefix(id, 'seconds') then
				days = num / (24 * 3600)
			else
				return
			end
			if days then
				return do_days(days)
			end
			if numstr:find('.', 1, true) then
				return
			end
			if is_sub then
				y = -y
				m = -m
			end
			assert(-11 <= m and m <= 11)
			y = lhs.year + y
			m = lhs.month + m
			if m > 12 then
				y = y + 1
				m = m - 12
			elseif m < 1 then
				y = y - 1
				m = m + 12
			end
			local d = math.min(lhs.day, days_in_month(y, m, lhs.calendar))
			return Date(lhs, y, m, d)
		end
	end
	if is_diff(rhs) then
		local days = rhs.age_days
		if (is_sub or false) ~= (rhs.isnegative or false) then
			days = -days
		end
		return lhs + days
	end
end

local full_date_only = {
	dayabbr = true,
	dayname = true,
	dow = true,
	dayofweek = true,
	dowiso = true,
	dayofweekiso = true,
	dayofyear = true,
	gsd = true,
	juliandate = true,
	jd = true,
	jdz = true,
	jdnoon = true,
}

-- Metatable for a date's calculated fields.
local datemt = {
	__index = function (self, key)
		if rawget(self, 'partial') then
			if full_date_only[key] then return end
			if key == 'monthabbr' or key == 'monthdays' or key == 'monthname' then
				if not self.month then return end
			end
		end
		local value
		if key == 'dayabbr' then
			value = day_info[self.dow][1]
		elseif key == 'dayname' then
			value = day_info[self.dow][2]
		elseif key == 'dow' then
			value = (self.jdnoon + 1) % 7  -- day-of-week 0=Sun to 6=Sat
		elseif key == 'dayofweek' then
			value = self.dow
		elseif key == 'dowiso' then
			value = (self.jdnoon % 7) + 1  -- ISO day-of-week 1=Mon to 7=Sun
		elseif key == 'dayofweekiso' then
			value = self.dowiso
		elseif key == 'dayofyear' then
			local first = Date(self.year, 1, 1, self.calendar).jdnoon
			value = self.jdnoon - first + 1  -- day-of-year 1 to 366
		elseif key == 'era' then
			-- Era text (never a negative sign) from year and options.
			value = get_era_for_year(self.options.era, self.year)
		elseif key == 'format' then
			value = self.options.format or 'dmy'
		elseif key == 'gsd' then
			-- GSD = 1 from 00:00:00 to 23:59:59 on 1 January 1 AD Gregorian calendar,
			-- which is from jd 1721425.5 to 1721426.49999.
			value = floor(self.jd - 1721424.5)
		elseif key == 'juliandate' or key == 'jd' or key == 'jdz' then
			local jd, jdz = julian_date(self)
			rawset(self, 'juliandate', jd)
			rawset(self, 'jd', jd)
			rawset(self, 'jdz', jdz)
			return key == 'jdz' and jdz or jd
		elseif key == 'jdnoon' then
			-- Julian date at noon (an integer) on the calendar day when jd occurs.
			value = floor(self.jd + 0.5)
		elseif key == 'isleapyear' then
			value = is_leap_year(self.year, self.calendar)
		elseif key == 'monthabbr' then
			value = month_info[self.month][1]
		elseif key == 'monthdays' then
			value = days_in_month(self.year, self.month, self.calendar)
		elseif key == 'monthname' then
			value = month_info[self.month][2]
		end
		if value ~= nil then
			rawset(self, key, value)
			return value
		end
	end,
}

-- Date operators.
local function mt_date_add(lhs, rhs)
	if not is_date(lhs) then
		lhs, rhs = rhs, lhs  -- put date on left (it must be a date for this to have been called)
	end
	return date_add_sub(lhs, rhs)
end

local function mt_date_sub(lhs, rhs)
	if is_date(lhs) then
		if is_date(rhs) then
			return DateDiff(lhs, rhs)
		end
		return date_add_sub(lhs, rhs, true)
	end
end

local function mt_date_concat(lhs, rhs)
	return tostring(lhs) .. tostring(rhs)
end

local function mt_date_tostring(self)
	return self:text()
end

local function mt_date_eq(lhs, rhs)
	-- Return true if dates identify same date/time where, for example,
	-- Date(-4712, 1, 1, 'Julian') == Date(-4713, 11, 24, 'Gregorian') is true.
	-- This is called only if lhs and rhs have the same type and the same metamethod.
	if lhs.partial or rhs.partial then
		-- One date is partial; the other is a partial or a full date.
		-- The months may both be nil, but must be the same.
		return lhs.year == rhs.year and lhs.month == rhs.month and lhs.calendar == rhs.calendar
	end
	return lhs.jdz == rhs.jdz
end

local function mt_date_lt(lhs, rhs)
	-- Return true if lhs < rhs, for example,
	-- Date('1 Jan 2016') < Date('06:00 1 Jan 2016') is true.
	-- This is called only if lhs and rhs have the same type and the same metamethod.
	if lhs.partial or rhs.partial then
		-- One date is partial; the other is a partial or a full date.
		if lhs.calendar ~= rhs.calendar then
			return lhs.calendar == 'Julian'
		end
		if lhs.partial then
			lhs = lhs.partial.first
		end
		if rhs.partial then
			rhs = rhs.partial.first
		end
	end
	return lhs.jdz < rhs.jdz
end

--[[ Examples of syntax to construct a date:
Date(y, m, d, 'julian')             default calendar is 'gregorian'
Date(y, m, d, H, M, S, 'julian')
Date('juliandate', jd, 'julian')    if jd contains "." text output includes H:M:S
Date('currentdate')
Date('currentdatetime')
Date('1 April 1995', 'julian')      parse date from text
Date('1 April 1995 AD', 'julian')   using an era sets a flag to do the same for output
Date('04:30:59 1 April 1995', 'julian')
Date(date)                          copy of an existing date
Date(date, t)                       same, updated with y,m,d,H,M,S fields from table t
Date(t)                       		date with y,m,d,H,M,S fields from table t
]]
function Date(...)  -- for forward declaration above
	-- Return a table holding a date assuming a uniform calendar always applies
	-- (proleptic Gregorian calendar or proleptic Julian calendar), or
	-- return nothing if date is invalid.
	-- A partial date has a valid year, however its month may be nil, and
	-- its day and time fields are nil.
	-- Field partial is set to false (if a full date) or a table (if a partial date).
	local calendars = { julian = 'Julian', gregorian = 'Gregorian' }
	local newdate = {
		_id = uniq,
		calendar = 'Gregorian',  -- default is Gregorian calendar
		hastime = false,  -- true if input sets a time
		hour = 0,  -- always set hour/minute/second so don't have to handle nil
		minute = 0,
		second = 0,
		options = {},
		list = _date_list,
		subtract = function (self, rhs, options)
			return DateDiff(self, rhs, options)
		end,
		text = _date_text,
	}
	local argtype, datetext, is_copy, jd_number, tnums
	local numindex = 0
	local numfields = { 'year', 'month', 'day', 'hour', 'minute', 'second' }
	local numbers = {}
	for _, v in ipairs({...}) do
		v = strip_to_nil(v)
		local vlower = type(v) == 'string' and v:lower() or nil
		if v == nil then
			-- Ignore empty arguments after stripping so modules can directly pass template parameters.
		elseif calendars[vlower] then
			newdate.calendar = calendars[vlower]
		elseif vlower == 'partial' then
			newdate.partial = true
		elseif vlower == 'fix' then
			newdate.want_fix = true
		elseif is_date(v) then
			-- Copy existing date (items can be overridden by other arguments).
			if is_copy or tnums then
				return
			end
			is_copy = true
			newdate.calendar = v.calendar
			newdate.partial = v.partial
			newdate.hastime = v.hastime
			newdate.options = v.options
			newdate.year = v.year
			newdate.month = v.month
			newdate.day = v.day
			newdate.hour = v.hour
			newdate.minute = v.minute
			newdate.second = v.second
		elseif type(v) == 'table' then
			if tnums then
				return
			end
			tnums = {}
			local tfields = { year=1, month=1, day=1, hour=2, minute=2, second=2 }
			for tk, tv in pairs(v) do
				if tfields[tk] then
					tnums[tk] = tonumber(tv)
				end
				if tfields[tk] == 2 then
					newdate.hastime = true
				end
			end
		else
			local num = tonumber(v)
			if not num and argtype == 'setdate' and numindex == 1 then
				num = month_number(v)
			end
			if num then
				if not argtype then
					argtype = 'setdate'
				end
				if argtype == 'setdate' and numindex < 6 then
					numindex = numindex + 1
					numbers[numfields[numindex]] = num
				elseif argtype == 'juliandate' and not jd_number then
					jd_number = num
					if type(v) == 'string' then
						if v:find('.', 1, true) then
							newdate.hastime = true
						end
					elseif num ~= floor(num) then
						-- The given value was a number. The time will be used
						-- if the fractional part is nonzero.
						newdate.hastime = true
					end
				else
					return
				end
			elseif argtype then
				return
			elseif type(v) == 'string' then
				if v == 'currentdate' or v == 'currentdatetime' or v == 'juliandate' then
					argtype = v
				else
					argtype = 'datetext'
					datetext = v
				end
			else
				return
			end
		end
	end
	if argtype == 'datetext' then
		if tnums or not set_date_from_numbers(newdate, extract_date(newdate, datetext)) then
			return
		end
	elseif argtype == 'juliandate' then
		newdate.partial = nil
		newdate.jd = jd_number
		if not set_date_from_jd(newdate) then
			return
		end
	elseif argtype == 'currentdate' or argtype == 'currentdatetime' then
		newdate.partial = nil
		newdate.year = current.year
		newdate.month = current.month
		newdate.day = current.day
		if argtype == 'currentdatetime' then
			newdate.hour = current.hour
			newdate.minute = current.minute
			newdate.second = current.second
			newdate.hastime = true
		end
		newdate.calendar = 'Gregorian'  -- ignore any given calendar name
	elseif argtype == 'setdate' then
		if tnums or not set_date_from_numbers(newdate, numbers) then
			return
		end
	elseif not (is_copy or tnums) then
		return
	end
	if tnums then
		newdate.jd = nil  -- force recalculation in case jd was set before changes from tnums
		if not set_date_from_numbers(newdate, tnums) then
			return
		end
	end
	if newdate.partial then
		local year = newdate.year
		local month = newdate.month
		local first = Date(year, month or 1, 1, newdate.calendar)
		month = month or 12
		local last = Date(year, month, days_in_month(year, month), newdate.calendar)
		newdate.partial = { first = first, last = last }
	else
		newdate.partial = false  -- avoid index lookup
	end
	setmetatable(newdate, datemt)
	local readonly = {}
	local mt = {
		__index = newdate,
		__newindex = function(t, k, v) error('date.' .. tostring(k) .. ' is read-only', 2) end,
		__add = mt_date_add,
		__sub = mt_date_sub,
		__concat = mt_date_concat,
		__tostring = mt_date_tostring,
		__eq = mt_date_eq,
		__lt = mt_date_lt,
	}
	return setmetatable(readonly, mt)
end

local function _diff_age(diff, code, options)
	-- Return a tuple of integer values from diff as specified by code, except that
	-- each integer may be a list of two integers for a diff with a partial date, or
	-- return nil if the code is not supported.
	-- If want round, the least significant unit is rounded to nearest whole unit.
	-- For a duration, an extra day is added.
	local wantround, wantduration, wantrange
	if type(options) == 'table' then
		wantround = options.round
		wantduration = options.duration
		wantrange = options.range
	else
		wantround = options
	end
	if not is_diff(diff) then
		local f = wantduration and 'duration' or 'age'
		error(f .. ': need a date difference (use "diff:' .. f .. '()" with a colon)', 2)
	end
	if diff.partial then
		-- Ignore wantround, wantduration.
		local function choose(v)
			if type(v) == 'table' then
				if not wantrange or v[1] == v[2] then
					-- Example: Date('partial', 2005) - Date('partial', 2001) gives
					-- diff.years = { 3, 4 } to show the range of possible results.
					-- If do not want a range, choose the second value as more expected.
					return v[2]
				end
			end
			return v
		end
		if code == 'ym' or code == 'ymd' then
			if not wantrange and diff.iszero then
				-- This avoids an unexpected result such as
				-- Date('partial', 2001) - Date('partial', 2001)
				-- giving diff = { years = 0, months = { 0, 11 } }
				-- which would be reported as 0 years and 11 months.
				return 0, 0
			end
			return choose(diff.partial.years), choose(diff.partial.months)
		end
		if code == 'y' then
			return choose(diff.partial.years)
		end
		if code == 'm' or code == 'w' or code == 'd' then
			return choose({ diff.partial.mindiff:age(code), diff.partial.maxdiff:age(code) })
		end
		return nil
	end
	local extra_days = wantduration and 1 or 0
	if code == 'wd' or code == 'w' or code == 'd' then
		local offset = wantround and 0.5 or 0
		local days = diff.age_days + extra_days
		if code == 'wd' or code == 'd' then
			days = floor(days + offset)
			if code == 'd' then
				return days
			end
			return floor(days/7), days % 7
		end
		return floor(days/7 + offset)
	end
	local H, M, S = diff.hours, diff.minutes, diff.seconds
	if code == 'dh' or code == 'dhm' or code == 'dhms' or code == 'h' or code == 'hm' or code == 'hms' then
		local days = floor(diff.age_days + extra_days)
		local inc_hour
		if wantround then
			if code == 'dh' or code == 'h' then
				if M >= 30 then
					inc_hour = true
				end
			elseif code == 'dhm' or code == 'hm' then
				if S >= 30 then
					M = M + 1
					if M >= 60 then
						M = 0
						inc_hour = true
					end
				end
			else
				-- Nothing needed because S is an integer.
			end
			if inc_hour then
				H = H + 1
				if H >= 24 then
					H = 0
					days = days + 1
				end
			end
		end
		if code == 'dh' or code == 'dhm' or code == 'dhms' then
			if code == 'dh' then
				return days, H
			elseif code == 'dhm' then
				return days, H, M
			else
				return days, H, M, S
			end
		end
		local hours = days * 24 + H
		if code == 'h' then
			return hours
		elseif code == 'hm' then
			return hours, M
		end
		return hours, M, S
	end
	if wantround then
		local inc_hour
		if code == 'ymdh' or code == 'ymwdh' then
			if M >= 30 then
				inc_hour = true
			end
		elseif code == 'ymdhm' or code == 'ymwdhm' then
			if S >= 30 then
				M = M + 1
				if M >= 60 then
					M = 0
					inc_hour = true
				end
			end
		elseif code == 'ymd' or code == 'ymwd' or code == 'yd' or code == 'md' then
			if H >= 12 then
				extra_days = extra_days + 1
			end
		end
		if inc_hour then
			H = H + 1
			if H >= 24 then
				H = 0
				extra_days = extra_days + 1
			end
		end
	end
	local y, m, d = diff.years, diff.months, diff.days
	if extra_days > 0 then
		d = d + extra_days
		if d > 28 or code == 'yd' then
			-- Recalculate in case have passed a month.
			diff = diff.date1 + extra_days - diff.date2
			y, m, d = diff.years, diff.months, diff.days
		end
	end
	if code == 'ymd' then
		return y, m, d
	elseif code == 'yd' then
		if y > 0 then
			-- It is known that diff.date1 > diff.date2.
			diff = diff.date1 - (diff.date2 + (y .. 'y'))
		end
		return y, floor(diff.age_days)
	elseif code == 'md' then
		return y * 12 + m, d
	elseif code == 'ym' or code == 'm' then
		if wantround then
			if d >= 16 then
				m = m + 1
				if m >= 12 then
					m = 0
					y = y + 1
				end
			end
		end
		if code == 'ym' then
			return y, m
		end
		return y * 12 + m
	elseif code == 'ymw' then
		local weeks = floor(d/7)
		if wantround then
			local days = d % 7
			if days > 3 or (days == 3 and H >= 12) then
				weeks = weeks + 1
			end
		end
		return y, m, weeks
	elseif code == 'ymwd' then
		return y, m, floor(d/7), d % 7
	elseif code == 'ymdh' then
		return y, m, d, H
	elseif code == 'ymwdh' then
		return y, m, floor(d/7), d % 7, H
	elseif code == 'ymdhm' then
		return y, m, d, H, M
	elseif code == 'ymwdhm' then
		return y, m, floor(d/7), d % 7, H, M
	end
	if code == 'y' then
		if wantround and m >= 6 then
			y = y + 1
		end
		return y
	end
	return nil
end

local function _diff_duration(diff, code, options)
	if type(options) ~= 'table' then
		options = { round = options }
	end
	options.duration = true
	return _diff_age(diff, code, options)
end

-- Metatable for some operations on date differences.
diffmt = {  -- for forward declaration above
	__concat = function (lhs, rhs)
		return tostring(lhs) .. tostring(rhs)
	end,
	__tostring = function (self)
		return tostring(self.age_days)
	end,
	__index = function (self, key)
		local value
		if key == 'age_days' then
			if rawget(self, 'partial') then
				local function jdz(date)
					return (date.partial and date.partial.first or date).jdz
				end
				value = jdz(self.date1) - jdz(self.date2)
			else
				value = self.date1.jdz - self.date2.jdz
			end
		end
		if value ~= nil then
			rawset(self, key, value)
			return value
		end
	end,
}

function DateDiff(date1, date2, options)  -- for forward declaration above
	-- Return a table with the difference between two dates (date1 - date2).
	-- The difference is negative if date1 is older than date2.
	-- Return nothing if invalid.
	-- If d = date1 - date2 then
	--     date1 = date2 + d
	-- If date1 >= date2 and the dates have no H:M:S time specified then
	--     date1 = date2 + (d.years..'y') + (d.months..'m') + d.days
	-- where the larger time units are added first.
	-- The result of Date(2015,1,x) + '1m' is Date(2015,2,28) for
	-- x = 28, 29, 30, 31. That means, for example,
	--     d = Date(2015,3,3) - Date(2015,1,31)
	-- gives d.years, d.months, d.days = 0, 1, 3 (excluding date1).
	if not (is_date(date1) and is_date(date2) and date1.calendar == date2.calendar) then
		return
	end
	local wantfill
	if type(options) == 'table' then
		wantfill = options.fill
	end
	local isnegative = false
	local iszero = false
	if date1 < date2 then
		isnegative = true
		date1, date2 = date2, date1
	elseif date1 == date2 then
		iszero = true
	end
	-- It is known that date1 >= date2 (period is from date2 to date1).
	if date1.partial or date2.partial then
		-- Two partial dates might have timelines:
		---------------------A=================B--- date1 is from A to B inclusive
		--------C=======D-------------------------- date2 is from C to D inclusive
		-- date1 > date2 iff A > C (date1.partial.first > date2.partial.first)
		-- The periods can overlap ('April 2001' - '2001'):
		-------------A===B------------------------- A=2001-04-01  B=2001-04-30
		--------C=====================D------------ C=2001-01-01  D=2001-12-31
		if wantfill then
			date1, date2 = autofill(date1, date2)
		else
			local function zdiff(date1, date2)
				local diff = date1 - date2
				if diff.isnegative then
					return date1 - date1  -- a valid diff in case we call its methods
				end
				return diff
			end
			local function getdate(date, which)
				return date.partial and date.partial[which] or date
			end
			local maxdiff = zdiff(getdate(date1, 'last'), getdate(date2, 'first'))
			local mindiff = zdiff(getdate(date1, 'first'), getdate(date2, 'last'))
			local years, months
			if maxdiff.years == mindiff.years then
				years = maxdiff.years
				if maxdiff.months == mindiff.months then
					months = maxdiff.months
				else
					months = { mindiff.months, maxdiff.months }
				end
			else
				years = { mindiff.years, maxdiff.years }
			end
			return setmetatable({
				date1 = date1,
				date2 = date2,
				partial = {
					years = years,
					months = months,
					maxdiff = maxdiff,
					mindiff = mindiff,
				},
				isnegative = isnegative,
				iszero = iszero,
				age = _diff_age,
				duration = _diff_duration,
			}, diffmt)
		end
	end
	local y1, m1 = date1.year, date1.month
	local y2, m2 = date2.year, date2.month
	local years = y1 - y2
	local months = m1 - m2
	local d1 = date1.day + hms(date1)
	local d2 = date2.day + hms(date2)
	local days, time
	if d1 >= d2 then
		days = d1 - d2
	else
		months = months - 1
		-- Get days in previous month (before the "to" date) given December has 31 days.
		local dpm = m1 > 1 and days_in_month(y1, m1 - 1, date1.calendar) or 31
		if d2 >= dpm then
			days = d1 - hms(date2)
		else
			days = dpm - d2 + d1
		end
	end
	if months < 0 then
		years = years - 1
		months = months + 12
	end
	days, time = math.modf(days)
	local H, M, S = h_m_s(time)
	return setmetatable({
		date1 = date1,
		date2 = date2,
		partial = false,  -- avoid index lookup
		years = years,
		months = months,
		days = days,
		hours = H,
		minutes = M,
		seconds = S,
		isnegative = isnegative,
		iszero = iszero,
		age = _diff_age,
		duration = _diff_duration,
	}, diffmt)
end


local z = {
	_current = current,
	_Date = Date,
	_days_in_month = days_in_month,
}


-- Aqui comienzas las funciones adaptadas de [[Módulo:Fecha]] --
----------------------------------------------------------------
----------------------------------------------------------------
----------------------------------------------------------------

function z.fechaActual()
	local d = os.date('!*t')
	
	local fecha = {}
	fecha.anyo    = d.year
	fecha.mes     = d.month
	fecha.dia     = d.day
	fecha.hora    = d.hour
	fecha.minuto  = d.min
	fecha.segundo = d.sec
		
	return fecha
end

function validar(fecha)
	fecha.anyo    = tonumber(fecha.anyo)
	fecha.mes     = tonumber(fecha.mes)
	fecha.dia     = tonumber(fecha.dia)
	fecha.hora    = tonumber(fecha.hora)
	fecha.minuto  = tonumber(fecha.minuto)
	fecha.segundo = tonumber(fecha.segundo)
	
	-- Falta validar que es una fecha válida
end

function z.edad(fecha1, fecha2)
	--Función que devuelve la edad en años entre dos fechas
	
	--Se supone que las fechas se han validado previamente.
	
	if not fecha1 then
		return -- falta devolver un error
	end
	
	if not fecha2 then
		fecha2=z.fechaActual()
	end
	
	local anyos = fecha2.anyo - fecha1.anyo
	
	--if true then return require('Módulo:Tablas').tostring(fecha2) end
	
	if fecha2.mes < fecha1.mes or
	   (fecha2.mes == fecha1.mes and fecha2.dia < fecha1.dia) then
	   	anyos = anyos - 1
	end
	
	if anyos < 0 then
		return -- falta devolver error
	elseif anyos == 0 then
		return 'menos de un año'
	elseif anyos == 1 then
		return 'un año'
	else
		return anyos .. ' años'
	end
end

function z.llamadaDesdeUnaPlantilla(frame)
	function obtenerFecha(dia, mes, anyo)
		local resultado={}
	
		if dia then
			resultado.dia  = dia
			resultado.mes  = mes
			resultado.anyo = anyo
			
			validar(resultado)
			
			return resultado
		end
	end

	local args = frame.args
	
	local funcion = z[args[1]]
	local fecha1  = obtenerFecha(args[2], args[3], args[4])
	local fecha2  = obtenerFecha(args[5], args[6], args[7])

	return funcion(fecha1, fecha2)
end
       

-- Aqui comienzas las funciones adaptadas de [[Módulo:Fechas]] --
-----------------------------------------------------------------
-----------------------------------------------------------------
-----------------------------------------------------------------


function z.NombreDelMes(mes)
  -- Función que devuelve el nombre del mes, donde mes es un número entre 1 y 12. 
  -- Si no es así se devuelve el valor de mes.
  -- Por ejemplo, 2  --> febrero
  --              02 --> febrero
  --              abril --> abril
  --              MAYO --> MAYO
   return month_info[tonumber(mes)][2] or mes
end

function z.Fecha(frame)
    -- Función que formatea una fecha
    -- El único parámetro obligatorio es el año o 3.
    -- Obtener los argumentos con los que se llama a la función

    local argumentos = {}
    local parent = {}
    if frame == mw.getCurrentFrame() then
        if   frame.args[3]  or frame.args["año"]  then
    	    argumentos = frame.args
        else 
    	    parent = frame:getParent()
            argumentos = parent.args
        end 
    else
        argumentos = frame
    end
    
    local enlace = argumentos["enlace"] ~= "no"

    -- Obtener el día, el nombre del mes y el año incluyendo para los años negativos a.d.
    local dia = argumentos["día"] or argumentos[1] or ''    
    local mes = argumentos["mes"] or argumentos[2] or '' 
    local anyo=tonumber(argumentos["año"] or argumentos[3]) or 0
	
    dia = (dia ~='') and (tonumber(dia) or dia) or dia -- Eliminar ceros a la izquierda del día.
    mes = (mes~='') and ((month_info[mes] and month_info[mes][2]) or month_info[tonumber(mes)][2] or mes) or mes -- Extraer nombre del mes
    anyo = (anyo<0) and (-anyo .. ' a. C.') or anyo
    
    local calendario = (argumentos["calendario"] == 'juliano') and ('<sup>[[Calendario juliano|jul.]]</sup>') or ''

    -- Formatear la fecha dependiendo de si el día, el mes o el año están informados
	local out = ''
	
	if anyo ~= 0 then
		out = enlace and (out .. '[[' .. anyo .. ']]') or tostring(anyo)
		if mes~='' then
			if argumentos["mayúscula"] == 'sí' then
				mes = mw.language.new('es'):ucfirst(mes)
			end
			out = enlace and (mes .. ']] de ' .. out) or (mes .. ' de ' .. out)
			if dia ~='' then
				out = enlace and ('[[' .. dia .. ' de ' .. out .. calendario) or (dia .. ' de ' .. out .. calendario)
			else
				out = enlace and ('[[' .. out) or out
			end	
		end
	 end
	return out
 end
 
 function z.Numerica(frame)
	local d = Date(frame.args[1])
    local err = '<strong class="error">Cadena de fecha no válida</strong>'
	return (d == nil) and err or d:text('%Y%m%d')
 end

 
return z