--[[
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
]]
local
p
=
{}
-- =======================================
-- === Dependencies ======================
-- =======================================
local
D
=
require
(
'Module:DateI18n/sandbox'
)
-- the enwp version of c:Module:Date
--[[
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
function
p
.
_ISOyear
(
input
)
if
not
input
then
return
''
end
input
=
mw
.
text
.
trim
(
input
)
-- if empty string then return it
if
input
==
""
then
return
input
end
-- if number then return it
if
tonumber
(
input
)
then
return
mw
.
ustring
.
format
(
'%04i'
,
input
)
end
-- otherwise use regular expression match
input
=
mw
.
ustring
.
match
(
input
,
'^+?(-?%d%d?%d?%d?)-'
)
if
input
and
tonumber
(
input
)
then
return
mw
.
ustring
.
format
(
'%04i'
,
input
)
else
return
''
end
end
--[[
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
datestr
,
succeded
local
args
=
frame
.
args
if
not
(
args
.
lang
and
mw
.
language
.
isSupportedLanguage
(
args
.
lang
))
then
args
.
lang
=
frame
:
callParserFunction
(
"int"
,
"lang"
)
-- get user's chosen language
end
datestr
,
succeded
=
p
.
_ISOdate
(
mw
.
text
.
trim
(
args
[
1
]),
args
.
lang
,
-- language
args
.
case
or
''
,
-- 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
function
p
.
_ISOdate
(
datestr
,
lang
,
case
,
class
,
trim_year
)
-- 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
,
tail
,
formatNum
datevec
,
tail
,
formatNum
=
p
.
test_date_formats
(
datestr
or
''
,
patterns
)
if
datevec
[
1
]
==
''
or
datevec
[
1
]
==
nil
then
-- quickly return if datestr does not look like date (it could be a template)
return
datestr
,
false
end
-- call p._Date function to format date string
local
succeded
,
datestr2
succeded
,
datestr2
=
pcall
(
D
.
_Date
,
datevec
,
lang
,
case
,
class
,
trim_year
)
if
succeded
and
datestr2
~=
''
then
return
mw
.
text
.
trim
(
datestr2
..
tail
),
true
else
-- in case of errors return the original string
return
datestr
,
false
end
end
function
p
.
ISOdate_extended
(
frame
)
-- 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
datestr
,
succeded
local
args
=
frame
.
args
if
not
(
args
.
lang
and
mw
.
language
.
isSupportedLanguage
(
args
.
lang
))
then
args
.
lang
=
frame
:
callParserFunction
(
"int"
,
"lang"
)
-- get user's chosen language
end
datestr
,
succeded
=
p
.
_ISOdate
(
mw
.
text
.
trim
(
args
[
1
]),
args
.
lang
,
-- language
args
.
case
or
''
,
-- 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
)
if
succeded
then
return
datestr
end
local
patterns
=
{
-- Exended set of recognized formats: like MM/DD/YYYY
{
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)$"
},
{
dlen
=
3
,
tail
=
0
,
regexp
=
"^(%d%d?)%s(%w+)%s(%d%d%d%d)$"
},
{
dlen
=
3
,
tail
=
0
,
regexp
=
"^(%w+)%s(%d%d?),%s(%d%d%d%d)$"
},
}
local
datevec
,
tail
,
formatNum
,
category
=
''
datevec
,
tail
,
formatNum
=
p
.
test_date_formats
(
frame
.
args
[
1
],
patterns
)
if
formatNum
==
1
or
formatNum
==
2
then
vec
=
datevec
;
if
tonumber
(
datevec
[
1
])
>
12
then
frame
.
args
[
1
]
=
string.format
(
'%04i-%02i-%02i'
,
datevec
[
3
],
datevec
[
2
],
datevec
[
1
]
)
category
=
'[[Category:Date in DD/MM/YYYY format]]'
return
mw
.
text
.
trim
(
p
.
ISOdate
(
frame
)
..
tail
);
elseif
tonumber
(
datevec
[
2
])
>
12
then
frame
.
args
[
1
]
=
string.format
(
'%04i-%02i-%02i'
,
datevec
[
3
],
datevec
[
1
],
datevec
[
2
]
)
category
=
'[[Category:Date in MM/DD/YYYY format]]'
return
mw
.
text
.
trim
(
p
.
ISOdate
(
frame
)
..
tail
);
end
elseif
(
formatNum
==
3
or
formatNum
==
4
)
and
(
datevec
[
3
]
==
''
or
datevec
[
3
]
~=
nil
)
then
local
str
=
mw
.
getCurrentFrame
():
callParserFunction
(
"#time"
,
{
'Y-m-d'
,
datestr
}
)
local
vec
=
{
str
:
match
(
"^(%d%d?%d?%d?)-(%d%d?)-(%d%d?)$"
)}
if
vec
and
vec
[
1
]
~=
nil
then
frame
.
args
[
1
]
=
string.format
(
'%04i-%02i-%02i'
,
vec
[
1
],
vec
[
2
],
vec
[
3
]
)
category
=
'[[Category:Date in word format]]'
return
p
.
ISOdate
(
frame
);
end
end
return
datestr
end
function
p
.
test_date_formats
(
datestr
,
patterns
)
-- pattern: regexp - regular expresion to test; dlen - number of date elements; tail = which element is a "tail" if any
local
datevec
=
{
''
,
''
,
''
,
''
,
''
,
''
}
local
tail
=
''
local
vec
,
pat
local
formatNum
=
0
for
i
,
pat
in
ipairs
(
patterns
)
do
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
formatNum
=
i
break
end
end
return
datevec
,
tail
,
formatNum
end
return
p