Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Documentation for this module may be created at Module:ISOdate/doc

--[[  
  __  __           _       _        ___ ____   ___      _       _       
 |  \/  | ___   __| |_   _| | ___ _|_ _/ ___| / _ \  __| | __ _| |_ ___ 
 | |\/| |/ _ \ / _` | | | | |/ _ (_)| |\___ \| | | |/ _` |/ _` | __/ _ \
 | |  | | (_) | (_| | |_| | |  __/_ | | ___) | |_| | (_| | (_| | ||  __/
 |_|  |_|\___/ \__,_|\__,_|_|\___(_)___|____/ \___/ \__,_|\__,_|\__\___|
     
This module is intended for processing of date strings.

Please do not modify this code without applying the changes first at Module:ISOdate/sandbox and testing 
at Module:ISOdate/sandbox/testcases and Module talk:ISOdate/sandbox/testcases.

Authors and maintainers:
* User:Parent5446 - original version of the function mimicking template:ISOdate
* User:Jarekt - original version of the functions mimicking template:Date and template:ISOyear
]]

-- =======================================
-- === Dependencies ======================
-- =======================================
local Date = require('Module:DateI18n')._Date

-- =======================================
-- === Local Functions ===================
-- =======================================

local function parse_ISOdate(datestr)
-- Core function of this module, which splits "datestr" contining date in ISO format into Year, month, day, ... components
-- Output is "datevec" array with numbers representing date components. We also return "tail" storing text following the date

	-- pattern: regexp - regular expresion to test; dlen - number of date elements; tail = which element is a "tail" if any
	-- regexp hints:
	--  1) Strings starting with "^" and ending with "$" indicate whole string match
	--  2) optional tail part copied as-is and following the main parsed part of the date have to be separated from the date by a whitespace, so "(\s.+)?"
	local patterns = {
		-- strings starting with YYYY-MM-DD HH:MM:SS. Year 4 digits (if we know seconds than it was within the last 100 years), the rest 1-2
		-- date and time can be separated by space or "T" and there could be a "Z" on the end indicating "Zulu" time zone
		{dlen=6, tail=7, regexp="^+?(%d%d%d%d)-(%d%d?)-(%d%d?)[ T](%d%d?):(%d%d?):(%d%d?)Z?(%s.*)"}, 
		{dlen=6, tail=0, regexp="^+?(%d%d%d%d)-(%d%d?)-(%d%d?)[ T](%d%d?):(%d%d?):(%d%d?)Z?$"}, 
		-- strings starting with YYYY-MM-DD HH:MM. Year 4 digits, the rest 1-2
		-- (if one knows hour and minute than it was probably after a year 1000)
		{dlen=5, tail=6, regexp="^+?(%d%d%d%d)-(%d%d?)-(%d%d?)[ T](%d%d?):(%d%d?)(%s.+)"},
		{dlen=5, tail=0, regexp="^+?(%d%d%d%d)-(%d%d?)-(%d%d?)[ T](%d%d?):(%d%d?)$"},
		-- strings starting with YYYY-MM-DD. Year 1-4 digits, the rest 1-2
		{dlen=3, tail=4, regexp="^+?(%d%d?%d?%d?)-(%d%d?)-(%d%d?)(%s.+)"},
		{dlen=3, tail=0, regexp="^+?(%d%d?%d?%d?)-(%d%d?)-(%d%d?)$"},
		-- strings starting with YYYY-MM. Year 3-4 digits, month 2 digits
		-- (want to avoit converting to dates strings like 10-5 = 5
		{dlen=2, tail=3, regexp="^+?(%d%d%d%d?)-(%d%d)(%s.+)"}, 
		-- if whole string is in YYYY-MM form: If Year 1-4 digits, month 1-2 digits
		{dlen=2, tail=0, regexp="^+?(%d%d?%d?%d?)-(%d%d?)$"}, 
		-- string starts with a number -> it has to be 3 or 4 digit long to be a year
		{dlen=1, tail=2, regexp="^+?(%d%d%d%d?)(%s.+)"},	
		 -- if whole string is a number (1-4 digit long) than it will be interpreted as a year
		{dlen=1, tail=0, regexp="^+?(%d%d?%d?%d?)$"},
	}
	
	-- create datevec based on which variables are provided
	local datevec = {}
	local tail    = ''
	for i, pat in ipairs( patterns ) do
		local vec = {datestr:match( pat.regexp )}
		if vec and vec[1]~=nil then
			for j=1, pat.dlen do
				datevec[j] = vec[j]
			end
			if pat.tail>0 and vec[pat.tail]~=nil then
				tail = mw.ustring.gsub(' ' .. vec[pat.tail], ' +', ' ')
			end
			break
		end
	end
	if not datevec[1] or datevec[1]=='' then
		-- quickly return if datestr does not look like date (it could be a template)
		return nil, nil
	end
	return datevec, tail
end

-- ==================================================
-- === External functions ===========================
-- ==================================================
local p = {}

-- ===========================================================================
-- === Version of the function to be called from other LUA codes
-- ===========================================================================

function p._ISOyear( datestr )
    
	-- if empty string then return it
	datestr = mw.text.trim(datestr or '' )
	if datestr == '' then
		return ''
	end
    
	-- if number then return it
	if tonumber( datestr ) then
		return mw.ustring.format( '%04i', datestr )
	end
    
	-- otherwise use regular expression match
	datestr = mw.ustring.match( datestr, '^+?(-?%d%d?%d?%d?)-' )
	if datestr and tonumber( datestr ) then
		return mw.ustring.format( '%04i', datestr )
	else
		return ''
	end
end

function p._ISOdate(datestr, lang, case, class, trim_year)
	datestr = mw.text.trim(datestr or '' )
	local datevec, tail = parse_ISOdate(datestr)
	if not datevec then	
		return datestr, false -- quickly return if datestr does not look like date (it could be a template)
	end

	-- call p._Date function to format date string
	local datestr2 = Date(datevec, lang, case, class, trim_year)
	if datestr2~='' then
		return mw.text.trim( datestr2 .. tail), true
	else -- in case of errors return the original string
		return datestr, false
	end	
end

-- ===========================================================================
-- === Versions of the function to be called from template namespace
-- ===========================================================================
--[[
ISOdate
 
This function is the core part of the ISOdate template. 
 
Usage:
{{#invoke:ISOdate|ISOdate|target_string|lang=}}
 
Parameters:
     1: The date string 
  lang: The language to display it in
  form: Language format (genitive, etc.) for some languages
 class: CSS class for the <time> node

 Error Handling:
   If the string does not look like it contain the proper ISO date than the function will return the original string.
   
   That is the preferred treatment for the template:Information (and similar templates) which calling it.
]]
function p.ISOdate(frame)
	local args = {}
	for name, value in pairs( frame.args ) do 
		if value ~= '' then -- nuke empty strings
			if type(name)=='string' then 
				name = string.gsub( string.lower(name), ' ', '_')
			end
			args[name] = value
		end
	end


	local datestr, succeded = p._ISOdate(
		args[1] or '',
		args.lang,                  -- language
		args.case,                  -- allows to specify grammatical case for the month for languages that use them
		args.class or 'dtstart',    -- allows to set the html class of the time node where the date is included. 
		args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is	
	)
	return datestr
end

--[[
ISOyear
 
This function returns year part of date string.
 
Usage:
{{#invoke:ISOdate|ISOyear|target_string}}
 
Parameters
    1: The date string 
 
Error Handling:
   If the string does not look like it contain the year than the function will not return anything.
   That is the preferred treatment for the template:Creator which is the main (only?) template calling it.
]]
function p.ISOyear( frame )
	 return p._ISOyear( frame.args[1] )
end

--[[
ISOdate_part
 
This function returns any part of the date string (if present)
 
Usage:
{{#invoke:ISOdate|ISOdate_part|target_string|part number}}
 
Parameters
    1: The date string 
    2: part number: 1-year, 2-month, 3-day, 4-hour, 5-minute, 6-second
 
]]
function p.ISOdate_part(frame)
	local date_str = mw.text.trim(frame.args[1] or '' )
	local part_num = tonumber(frame.args[2] or '1')
	local datevec, tail = parse_ISOdate(date_str)
	return tonumber(datevec[part_num] or '0')
end

return p