Modul:DateUtils
Documentation for this module may be created at Modul:DateUtils/doc
local p = {}
local maxDaysInMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
local roman = require('Modul:Roman')
local getArgs = require('Modul:Arguments').getArgs
local defaultPostfixYear = { bc = 'î.Hr.', ad = 'd.Hr.' }
local linkingPostfixYear = { bc = 'î.Hr.', ad = 'd.Hr.' }
local months = mw.loadData('Modul:DateUtils/data').months
local suffixFormatYear = function(y, postFixYear)
local postFixYear = postFixYear or defaultPostfixYear
return (y < 0 and (' ' .. postFixYear.bc) or (y < 1000 and (' ' .. postFixYear.ad) or ''))
end
p.formatYear = function(y, link)
local out = ''
local yearLink = tostring(math.abs(y)) .. (y < 0 and suffixFormatYear(y, linkingPostfixYear) or '')
local yearLabel = tostring(math.abs(y)) .. suffixFormatYear(y, defaultPostfixYear)
if link then
out = out .. '[['
if yearLabel == yearLink then out = out .. yearLink
else out = out .. yearLink .. '|' .. yearLabel end
out = out .. ']]'
else
out = yearLabel
end
return out
end
p.surroundWithTimeTag = function(out, tagDate)
local timeTag = mw.html.create('time')
if tagDate.year > 0 then
local datetimeFormat = 'Y-m-d'
if tagDate.precision == 10 then
datetimeFormat = 'Y-m'
tagDate.day = 1
end
local intermediateFormatDate = os.date('%d %B %Y', os.time(tagDate))
timeTag:attr('datetime', mw.language.getContentLanguage():formatDate(datetimeFormat, intermediateFormatDate))
end
timeTag:wikitext(out)
return tostring(timeTag)
end
p.formatDate = function(indate, link, notimetag, dateFormat)
if not indate then return nil end
if indate.precision == 6 then
local out = 'mileniul '
if indate.year >= 2000 or indate.year <= -2000 then out = out .. 'al ' end
out = out .. roman.main({tostring(1 + math.floor((math.abs(indate.year) - 1) / 1000))})
if indate.year >= 2000 or indate.year <= -2000 then out = out .. '-lea' end
out = out .. suffixFormatYear(indate.year)
return out
end
if indate.precision == 7 then
local out = 'secolul '
if indate.year >= 200 or indate.year <= -200 then out = out .. 'al ' end
out = out .. roman.main({tostring(1 + math.floor((math.abs(indate.year) - 1) / 100))})
if indate.year >= 200 or indate.year <= -200 then out = out .. '-lea' end
out = out .. suffixFormatYear(indate.year)
return out
end
if indate.precision == 8 then
return 'anii ' .. tostring(math.floor(math.abs(indate.year) / 10) * 10) .. suffixFormatYear(indate.year)
end
if indate.precision == 9 then
if notimetag then return p.formatYear(indate.year, link) end
local timeTag = mw.html.create('time')
if indate.year > 0 then timeTag:attr('datetime', tostring(indate.year)) end
timeTag:wikitext(p.formatYear(indate.year, link))
return tostring(timeTag)
end
if indate.precision and indate.precision > 9 then
local d1 = {}
d1.day = indate.day
if not d1.day or d1.day == 0 then d1.day = 1 end
d1.month = indate.month
if not d1.month or d1.month == 0 then d1.month = 1 end
d1.year = math.abs(indate.year)
local out = ''
local intermediateFormatDate = os.date('%d %B %Y', os.time(d1))
if dateFormat then
out = out .. mw.language.getContentLanguage():formatDate(dateFormat, intermediateFormatDate)
else
out = out .. mw.language.getContentLanguage():formatDate((indate.precision >= 11) and (link and '[[j F]]' or 'j F') or (link and '[[F]]' or 'F'), intermediateFormatDate)
out = out .. ' ' .. p.formatYear(indate.year, link)
end
if notimetag or indate.precision < 11 then return out end
return p.surroundWithTimeTag(out, indate)
end
end
p.formatDateFromFrame = function(frame)
local args = getArgs(frame)
local dateObj = {}
if args[1] then
dateObj = p.parseDate(args[1])
end
if args[2] then
dateObj.month = tonumber(months[args[2]] or args[2])
if args[3] then dateObj.day = tonumber(args[3]) end
end
dateObj.calendar = 'gregorian'
if not dateObj.precision then
dateObj.precision = 9
end
if args[2] ~= nil then
dateObj.precision = 10
if args[3] ~= nil then
dateObj.precision = 11
end
end
return p.formatDate(dateObj, args['link'] ~= nil, args['notimetag'] ~= nil)
end
p.isDateGregorian = function(indate)
return indate.calendarmodel == 'http://www.wikidata.org/entity/Q1985727' or indate.calendarmodel == 'http://www.wikidata.org/entity/Q12138' or indate.calendar == 'gregorian'
end
p.isDateJulian = function(indate)
return indate.calendarmodel == 'http://www.wikidata.org/entity/Q1985786' or indate.calendarmodel == 'http://www.wikidata.org/entity/Q11184' or indate.calendar == 'julian'
end
p.isLeapYearGregorian = function(year)
if (year % 4 ~= 0) then return false
elseif (year % 100 ~= 0) then return true
elseif (year % 400 ~= 0) then return false
end
return true
end
p.isDateInLeapYear = function(indate)
if p.isDateJulian(indate) then
return 0 == indate.year % 4
end
return p.isLeapYearGregorian(indate.year)
end
p.addDaysToDate = function(indate, days)
local outdate = mw.clone(indate)
outdate.day = outdate.day + days
local lastDayOfMonth = maxDaysInMonth[math.fmod(outdate.month-1, 12)+1]
while outdate.day > lastDayOfMonth do
mw.logObject(outdate, "outdate")
lastDayOfMonth = maxDaysInMonth[math.fmod(outdate.month-1, 12)+1]
if outdate.month == 2 and p.isDateInLeapYear(outdate) then lastDayOfMonth = 29 end
outdate.month = outdate.month + 1
outdate.day = outdate.day - lastDayOfMonth
end
while outdate.month > 12 do
outdate.year = outdate.year + 1
outdate.month = outdate.month - 12
end
return outdate
end
p.parseCentury = function(datetext)
if datetext and mw.ustring.len(datetext) < 9 then return nil end
local centuryPrefixExpected = mw.ustring.sub(mw.ustring.lower(datetext), 1, 7)
if centuryPrefixExpected == 'secolul' then
local alLeaMatcherFunction = mw.ustring.gmatch(mw.ustring.lower(datetext), '%s+al%s+([xivlcm]+)%-lea')
local centNum = nil
local centStr = nil
if alLeaMatcherFunction then
centStr = alLeaMatcherFunction()
end
if not centStr then
local nonAlLEaMatcherFunction = mw.ustring.gmatch(mw.ustring.lower(datetext), '%s+([xivlcm]+)%s*')
if nonAlLEaMatcherFunction then
centStr = nonAlLEaMatcherFunction()
end
end
if not centStr then return nil end
local romanTestIdx = 1
while romanTestIdx < 30 do
if mw.ustring.lower(roman.main({tostring(romanTestIdx)})) == mw.ustring.lower(centStr) then
centNum = romanTestIdx
break
end
romanTestIdx = romanTestIdx + 1
end
if not centNum then return nil end
local bcPatterns = {}
table.insert(bcPatterns, 'î%.e%.n%.?')
table.insert(bcPatterns, 'î%.%s*Hr%.')
for _,eachBCPattern in ipairs(bcPatterns) do
local eraMatchFunction = mw.ustring.gmatch(datetext, eachBCPattern)
if eraMatchFunction then
local eraMatch = eraMatchFunction()
if eraMatch and eraMatch ~= '' then
if centNum > 0 then centNum = -centNum end
end
end
end
return centNum
end
return nil
end
p.parseYear = function(datetxt)
if (not mw.ustring.gmatch(datetxt, '^[%d%sîd%.HrenADBC]+$')) then
return nil
end
local yearPattern = '%d+'
local bcPatterns = {'î%.e%.n%.?', 'î%.%s*Hr%.', 'BC'}
local yearMatchFunction = mw.ustring.gmatch(datetxt, yearPattern)
if not yearMatchFunction then return nil end
local d = {}
local yearMatch = yearMatchFunction()
if not yearMatch or yearMatch == '' then return nil end
d.year = tonumber(yearMatch)
d.precision = 9
for _,eachBCPattern in ipairs(bcPatterns) do
local eraMatchFunction = mw.ustring.gmatch(datetxt, eachBCPattern)
if eraMatchFunction then
local eraMatch = eraMatchFunction()
if eraMatch and eraMatch ~= '' then
if d.year > 0 then d.year = -d.year end
end
end
end
return d
end
p.parseDate = function(datetxt)
if not datetxt then return nil end
local parsers = {}
local stdDateParser = {
pattern = '((%d%d%d%d)-(%d%d?)-(%d%d?))',
patternIsMatched = function(matchArray)
return tonumber(matchArray[2]) < 13 and tonumber(matchArray[3]) < 32
end,
extractDateFromText = function(matchArray)
local d = {}
d.day = tonumber(matchArray[3])
d.month = tonumber(matchArray[2])
d.year = tonumber(matchArray[1])
d.precision = 11
return d
end
}
table.insert(parsers, stdDateParser)
local noHyphensStdDateParser = {
pattern = '((%d%d%d%d)(%d%d)(%d%d))',
patternIsMatched = stdDateParser.patternIsMatched,
extractDateFromText = stdDateParser.extractDateFromText
}
table.insert(parsers, noHyphensStdDateParser)
local roDateParser = {
pattern = '((%d+)%s+(%a+)%s+(%d+))',
patternIsMatched = function(matchArray)
return matchArray[1] and mw.ustring.len(mw.text.trim(matchArray[1])) > 0 and matchArray[2] and months[matchArray[2]] ~= nil
end,
extractDateFromText = function(matchArray)
local d = {}
d.day = tonumber(matchArray[1])
d.month = tonumber(months[matchArray[2]])
d.year = tonumber(matchArray[3])
d.precision = 11
return d
end
}
table.insert(parsers, roDateParser)
local slashedDateParser = {
pattern = '((%d%d)/(%d%d)/(%d%d%d%d))',
patternIsMatched = function(matchArray)
return matchArray[1] and tonumber(matchArray[1]) < 32 and matchArray[2] and tonumber(matchArray[2]) < 13
end,
extractDateFromText = function(matchArray)
local d = {}
d.day = tonumber(matchArray[1])
d.month = tonumber(matchArray[2])
d.year = tonumber(matchArray[3])
d.precision = 11
return d
end
}
table.insert(parsers, slashedDateParser)
local enDateParser = {
pattern = '((%a+)%s+(%d+),%s+(%d+))',
patternIsMatched = function(matchArray)
return matchArray[2] and mw.ustring.len(mw.text.trim(matchArray[2])) > 0 and matchArray[1] and months[matchArray[1]] ~= nil
end,
extractDateFromText = function(matchArray)
local d = {}
d.day = tonumber(matchArray[2])
d.month = tonumber(months[matchArray[1]])
d.year = tonumber(matchArray[3])
d.precision = 11
return d
end
}
table.insert(parsers, enDateParser)
local monthOnlyParser = {
pattern = '((%a+)%s+(%d+))',
patternIsMatched = function(matchArray)
return matchArray[1] and months[matchArray[1]] ~= nil
end,
extractDateFromText = function(matchArray)
local d = {}
d.month = tonumber(months[matchArray[1]])
d.year = tonumber(matchArray[2])
d.precision = 10
return d
end
}
table.insert(parsers, monthOnlyParser)
local monthOnlyNumericParser = {
pattern = '((%d+)-(%d+))',
patternIsMatched = function(matchArray)
return matchArray[1] and matchArray[2]
end,
extractDateFromText = function(matchArray)
local d = {}
d.month = tonumber(matchArray[2])
d.year = tonumber(matchArray[1])
d.precision = 10
return d
end
}
table.insert(parsers, monthOnlyNumericParser)
for _,eachParser in ipairs(parsers) do
local eachMatchSet = {mw.ustring.gmatch(datetxt, eachParser.pattern)()}
if eachMatchSet[1] == datetxt then
table.remove(eachMatchSet, 1)
if eachParser.patternIsMatched(eachMatchSet) then
local d = eachParser.extractDateFromText(eachMatchSet)
if d ~= nil then
return d
end
end
end
end
local yr = p.parseYear(datetxt)
if yr ~= nil then
return yr
end
local cent = p.parseCentury(datetxt)
if cent ~= nil then
local d = {}
d.year = tonumber(cent * 100)
d.precision = 7
return d
end
return nil
end
p.parseWikidataDate = function(datetxt, precision)
if not datetxt then return nil end
if precision == nil then precision = 11 end
local iSOTimeSign = mw.ustring.sub(datetxt, 1, 1)
local datePattern = '(%d+)-(%d+)-(%d+)'
local matchesIterator = mw.ustring.gmatch(mw.ustring.sub(datetxt, 2), datePattern)
local yearSign = 1
--local timePattern = '(%d+):(%d+):(%d+)'
--local matchestimeIterator = mw.ustring.gmatch(datetxt, timePattern)
local yearStr, monthStr, dayStr = matchesIterator()
if dayStr and tonumber(dayStr) == 0 then dayStr = "01" end
if monthStr and tonumber(monthStr) == 0 then monthStr = "01" end
if iSOTimeSign == "-" then yearSign = -1 end
if precision >= 11 and dayStr and monthStr and yearStr then
local d = {}
d.day = tonumber(dayStr)
d.month = tonumber(monthStr)
d.year = tonumber(yearStr) * yearSign
d.precision = precision
return d
end
if precision == 10 and monthStr and yearStr then
local d = {}
d.day = 1 --this is a "valid 0"
d.month = tonumber(monthStr)
d.year = tonumber(yearStr) * yearSign
d.precision = precision
return d
end
if precision <= 9 and yearStr then
local d = {}
d.day = 1 --this is a "valid 0"
d.month = 1 --this is a "valid 0"
d.year = tonumber(yearStr) * yearSign
d.precision = precision
return d
end
return nil
end
p.compare = function(d1, d2)
if not d1.year and d2.year then return 1 end
if not d2.year and d1.year then return -1 end
if not d1.month and d2.month then
if d1.year == d2.year then
return 1
else
return d1.year - d2.year
end
end
if not d2.month and d1.month then
if d1.year == d2.year then
return -1
else
return d1.year - d2.year
end
end
if not d1.day and d2.day then
if d1.year == d2.year then
if d1.month == d2.month then
return 1
else
return d1.month - d2.month
end
else
return d1.year - d2.year
end
end
if not d2.day and d1.day then
if d1.year == d2.year then
if d1.month == d2.month then
return -1
else
return d1.month - d2.month
end
else
return d1.year - d2.year
end
end
if d1.year == d2.year then
if d1.month == d2.month then
if d1.day == d2.day then
return 0
else return (d1.day - d2.day) / math.abs(d1.day - d2.day)
end
else return (d1.month - d2.month) / math.abs(d1.month - d2.month)
end
else return (d1.year - d2.year) / math.abs(d1.year - d2.year)
end
end
p.extractDateFromWikidataSnak = function(snak)
if snak.snaktype ~= 'value' or not snak.datavalue then return nil end
local timestamp = snak.datavalue.value.time
local precision = snak.datavalue.value.precision
return p.parseWikidataDate(timestamp, precision)
end
p.toISO8601 = function(s)
if not s then return nil end
local d = type(s) == 'string' and p.parseDate(s) or s
return mw.language.getContentLanguage():formatDate('Y-m-d', os.date('%d %B %Y', os.time(d)))
end
p.addDaysToDateFromFrame = function(frame)
local args = getArgs(frame)
local days = args.delta and tonumber(args.delta) or 1
local today
if args.referenceDate then
today = p.parseDate(args.referenceDate)
else
today = os.date("*t")
today.precision = 11
end
local tomorrow = p.addDaysToDate(today, days)
return p.formatDate(tomorrow, false, args.notimetag ~= nil, args.dateFormat)
end
function p.daysBetween(d1, d2)
if d1.precision < 10 then
d1.month = 1
end
if d1.precision < 11 then
d1.day = 1
end
if d2.precision < 10 then
d2.month = 12
end
if d2.precision < 11 then
d2.day = maxDaysInMonth[d1.month]
end
local time1 = os.time(d1)
local time2 = os.time(d2)
local secsdiff = os.difftime(time2, time1)
return secsdiff / 3600 / 24
end
return p