אונטערשייד צווישן ווערסיעס פון "יחידה:דאטום"

פון המכלול
קפיצה לניווט קפיצה לחיפוש
ק (תנא קמא האט אריבערגעפירט בלאט יחידה:תאריך צו יחידה:דאטום אן לאזן א ווייטערפירונג)
(אויך MDY (אפריל 2, 1990) זאל ארבעטן)
צייכן: צוריקגעשטעלט
שורה 261: שורה 261:
      
      
     return year, month, day
     return year, month, day
end
--[[ get month name and day (as numeric string), in either order, and return their numbers as day, month
    if optional parameter 'reverse' is true, return month first
]]--
local function monthDay(dateTable, reverse)
local months = {
['יאנואר']= 1,
['פעברואר']= 2,
['מערץ']= 3,
['אפריל']= 4,
['מאי']= 5,
['יוני']= 6,
['יולי']= 7,
['אויגוסט']= 8,
['סעפטעמבער']= 9,
['אקטאבער']= 10,
['נאוועמבער']= 11,
['דעצעמבער']= 12
}
local month = nil
    local day = nil
    for _, value in ipairs(dateTable) do
        if months[value] then
            month = months[value]
        else
            day = tonumber(value)
        end
    end
    if reverse then
        return month, day
    else
        return day, month
    end
end
end


שורה 430: שורה 466:
]]
]]
function Date.newFromWikitext( wikitext )
function Date.newFromWikitext( wikitext )
local months = {
['יאנואר']= 1,
['פעברואר']= 2,
['מערץ']= 3,
['אפריל']= 4,
['מאי']= 5,
['יוני']= 6,
['יולי']= 7,
['אויגוסט']= 8,
['סעפטעמבער']= 9,
['אקטאבער']= 10,
['נאוועמבער']= 11,
['דעצעמבער']= 12
}
local calendar = nil
local calendar = nil
if mw.ustring.find( wikitext, '<small>%(%[%[יוליאנישער קאלענדאר%|יוליאניש%]%]%)</small>' ) then
if mw.ustring.find( wikitext, '<small>%(%[%[יוליאנישער קאלענדאר%|יוליאניש%]%]%)</small>' ) then
שורה 459: שורה 481:
-- BC to minus
-- BC to minus
wikitext = mw.ustring.gsub( wikitext, "([0-9]+) לפנה[\"״]ס" , "-%1")
wikitext = mw.ustring.gsub( wikitext, "([0-9]+) לפנה[\"״]ס" , "-%1")
for a in pairs(months) do
wikitext = mw.ustring.gsub(wikitext, ' ?ב?'..a, ' ' .. months[a])
end


if mw.ustring.match(wikitext, '^המאה ה[־-]%d+$') then
if mw.ustring.match(wikitext, '^המאה ה[־-]%d+$') then
שורה 468: שורה 486:
                 return Date.new( { year=tonumber(yearStr)*100, month=0, day=0, precision= Date.PRECISION.YEAR100 } )
                 return Date.new( { year=tonumber(yearStr)*100, month=0, day=0, precision= Date.PRECISION.YEAR100 } )
end
end
-- if there are alphabet chars return nil (unexpected character)
 
assert(not mw.ustring.find(wikitext, '%a'), "Unexpected format")
local parts = mw.text.split(mw.text.trim(wikitext),' ')
local parts = mw.text.split(mw.text.trim(wikitext),' ')


שורה 476: שורה 493:
if #parts==3 then -- DMY date
if #parts==3 then -- DMY date
definition.year = tonumber(parts[3])
definition.year = tonumber(parts[3])
definition.month = tonumber(parts[2])
definition.day, definition.month = monthDay({parts[1], parts[2]})
definition.day = tonumber(parts[1])
assert(definition.year, "Could not recognize year")
assert(definition.year, "Could not recognize year")
assert(definition.month<13 and definition.month>0, "Could not recognize month number")
assert(definition.month<13 and definition.month>0, "Could not recognize month number")
שורה 483: שורה 499:
definition.precision = Date.PRECISION.DAY
definition.precision = Date.PRECISION.DAY
elseif  #parts==2 then -- MY date
elseif  #parts==2 then -- MY date
definition.year = tonumber(parts[2])
definition.month, definition.year = monthDay(parts, true)
definition.month = tonumber(parts[1])
definition.precision = Date.PRECISION.MONTH
definition.precision = Date.PRECISION.MONTH
assert(definition.year<1e7, "Could not recognize year")
assert(definition.year<1e7, "Could not recognize year")

רעוויזיע פון 09:06, 24 מאי 2023

דער מאדול איז געווידמעט אויסצופירן פארשידענע פעולות אויף דאטומען.

  • #רעכן - מקבלת תאריך טקסטואלי ופורמט ומחלצת את מהתאריך את הפורמט הרצוי
  • #רעכן אפשטאנד - מקבלת טווח תאריכים ומחשבת את ההפרש ביניהם

רעכן

פארמאט ביישפיל אויסקום
טאג {{#invoke:דאטום|רעכן|[[3 פעברואר]] [[2013]]|טאג}} 3
טאג {{#invoke:דאטום|רעכן|[[3 פעברואר]] [[2013]]{{הערה|א}}|טאג}} 3
מאנאט {{#invoke:דאטום|רעכן|[[3 פעברואר]] [[2013]]|מאנאט}} 2
יאר {{#invoke:דאטום|רעכן|[[3 פעברואר]] [[2013]]|יאר}} 2013
יאר {{#invoke:דאטום|רעכן|[[פעברואר]] [[2013]]|יאר}} 2013
TS {{#invoke:דאטום|רעכן|[[3 פעברואר]] [[2013]]|TS}} 2013-02-03

הערות:

  • דעם פארמאט TS קען מען אריינגעבן אין צייט פונקציעס פון פארזער פונקציאנען.
  • ההמרה מתעלמת מסוגריים מרובעות של קישורים
  • מען קען צולייגן א פאראמעטער error והתוכן שלו יוצג במקרה של שגיאה בתאריך. (רצוי להכניס במקרה כזה את הדף לקטגוריה כלשהי שתאפשר מעקב אחרי דפים לא תקינים. ברירת המחדל היא קאטעגאריע:בלעטער מיט דאטום פעלערן)

רעכן אפשטאנד

ביישפיל אויסקום
דיפאלט פארמאט: אויטאמאטיש {{#invoke:דאטום|רעכן אפשטאנד|[[3 פעברואר]] [[1947]] - [[15 סעפטעמבער]] [[2013]]}} 66 יאָרן
דיפאלט פארמאט: אויטאמאטיש (דוגמה עם שנים וסימן מינוס) {{#invoke:דאטום|רעכן אפשטאנד|1947 - 2013}} בערך 66 יאָרן
דיפאלט פארמאט: אויטאמאטיש (דוגמה עם שנים וסימן קו מפריד) {{#invoke:דאטום|רעכן אפשטאנד|1947–2013}} בערך 66 יאָרן
יארן {{#invoke:דאטום|רעכן אפשטאנד|[[3 פעברואר]] [[1947]] - [[15 סעפטעמבער]] [[2013]]|יארן}} 66 יאָרן
גיל {{#invoke:דאטום|רעכן אפשטאנד|1 מערץ 1922 - 4 נאוועמבער 1995|גיל}} 73 יאָרן
גיל {{#invoke:דאטום|רעכן אפשטאנד|1 מערץ 1922{{הערה|ב}} - 4 נאוועמבער 1995|גיל}} 73 יאָרן
גיל {{#invoke:דאטום|רעכן אפשטאנד|1 מערץ 1994{{הערה|ב}} - 4 נאוועמבער 1995|גיל}} 1 יאָר
גיל {{#invoke:דאטום|רעכן אפשטאנד|1 מערץ 1993{{הערה|ב}} - 4 נאוועמבער 1995|גיל}} 2 יאָרן
מספר {{#invoke:דאטום|רעכן אפשטאנד|1 מערץ 1922 - 4 נאוועמבער 1995|מספר}} לוא־פעלער אין שורה 967: attempt to compare number with nil.
מספר {{#invoke:דאטום|רעכן אפשטאנד|1 מערץ 1922{{הערה|ב}} - 4 נאוועמבער 1995|מספר}} לוא־פעלער אין שורה 967: attempt to compare number with nil.
מספר {{#invoke:דאטום|רעכן אפשטאנד|1 מערץ 1994{{הערה|ב}} - 4 נאוועמבער 1995|מספר}} לוא־פעלער אין שורה 967: attempt to compare number with nil.
מספר {{#invoke:דאטום|רעכן אפשטאנד|1 מערץ 1993{{הערה|ב}} - 4 נאוועמבער 1995|מספר}} לוא־פעלער אין שורה 967: attempt to compare number with nil.
שנים לפנה"ס {{#invoke:דאטום|רעכן אפשטאנד|[[3 פעברואר]] 1900 לפנה"ס - [[15 סעפטעמבער]] [[2013]]|יארן}} 3,913 יאָרן
טעג {{#invoke:דאטום|רעכן אפשטאנד|[[3 פעברואר]] [[1947]] - [[15 סעפטעמבער]] [[2013]]|ימים}} 24,331 טעג
הפרש {{#invoke:דאטום|רעכן אפשטאנד|[[3 פעברואר]] [[1947]] - [[15 סעפטעמבער]] [[2013]]|הפרש}} 2102198400
ללא תאריך יעד {{#invoke:דאטום|רעכן אפשטאנד|[[3 פעברואר]] [[1947]]|יארן}} 77 יאָרן
נישט כולל לעצטן טאג {{#invoke:דאטום|רעכן אפשטאנד|5 יוני 1967 - 10 יוני 1967}} 5 טעג
כולל לעצטן טאג {{#invoke:דאטום|רעכן אפשטאנד|5 יוני 1967 - 10 יוני 1967|כולל=כן}} 6 טעג
כולל לעצטן טאג {{#invoke:דאטום|רעכן אפשטאנד|14 סעפטעמבער 2014 - 14 סעפטעמבער 2014|כולל=כן}}
יאר און טאג {{#invoke:דאטום|רעכן אפשטאנד|14 סעפטעמבער 2014 - 15 סעפטעמבער 2015|כולל=כן}} 1 יאָר

הערות:

  • אן א ציל דאטע ווערט די היינטיקע דאטע געניצט פאר דער ציל דאטע
  • הפרש - מציין את הפרש הזמנים בשניות וללא מילים. ניתן להשתמש בערך המוחזר בחלוקה מתאימה לקבלת יחידות זמן אחרות.
  • אז מען גיט איין דעם פאראמעטער כולל=כן, ואז נלקח בחשבון אויך דעם לעצטן טאג. כשמדובר על טווח זמן ארוך והפורמט נבחר אוטומטי או שמכיל שנים, מתקבלת תוצאה מקורבת.
  • ניתן להוסיף פרמטר error והתוכן שלו יוצג במקרה של שגיאה בטווח התאריכים. (רצוי להכניס במקרה כזה את הדף לקטגוריה כלשהי שתאפשר מעקב אחרי דפים לא תקינים. ברירת המחדל היא קאטעגאריע:בלעטער מיט דאטום פעלערן)
  • אם לא מצוין פורמט, הוא נבחר אוטומטית:
    • ביז 3 וואכן ווערט אויסגעשטעלט אין טעג
    • ביז א יאר ווערט אויסגעשטעלט אין וואכן און טעג
    • ביז 10 יאר אין יארן און וואכן
    • העכער פון דעם נאר אין יארן



  1. א
  2. ב
  3. ב
  4. ב
  5. ב
  6. ב
  7. ב
local Date = {}
local maxDaysInMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}

--[[
    Supported calendar models
]]--
Date.CALENDAR = {
	GREGORIAN = 'Gregorian',
	JULIAN    = 'Julian'
}

--Internal functions
--[[
    Check if a value is a number in the given range
    @param mixed value
    @param number min
    @param number max
    @return boolean
]]--
local function validateNumberInRange( value, min, max )
    return type( value ) == 'number' and value >= min and value <= max
end

--[[
    Validate a time defintion
    @param table definition data
    @return boolean
]]--
local function validate(definition)
	--Validate constants
	if not Date.knowsPrecision(definition.precision) or 
		(definition.calendar ~= Date.CALENDAR.GREGORIAN and definition.calendar ~= Date.CALENDAR.JULIAN) then
		return false
	end

    --Validate year
    if not (type( definition.year ) == 'number' or (definition.year == nil and precision == Date.PRECISION.DAY)) then
        return false
    end
    if definition.precision <= Date.PRECISION.YEAR then
        return true
    end

    --Validate month
    if not validateNumberInRange( definition.month, 1, 12 ) then
        return false
    end
    if definition.precision <= Date.PRECISION.MONTH then
        return true
    end

    --Validate day
    if not validateNumberInRange( definition.day, 1, 31 ) then
        return false
    end
    if definition.precision <= Date.PRECISION.DAY then
        return true
    end

    --Validate hour
    if not validateNumberInRange( definition.hour, 0, 23 ) then
        return false
    end
    if definition.precision <= Date.PRECISION.HOUR then
        return true
    end

    --Validate minute
    if not validateNumberInRange( definition.minute, 0, 59 ) then
        return false
    end
    if definition.precision <= Date.PRECISION.MINUTE then
        return true
    end

    --Validate second
    if not validateNumberInRange( definition.second, 0, 60 ) then
        return false
    end

    return true
end

--[[
    Try to find the relevant precision for a time definition
    @param table time definition
    @return number the precision
]]--
local function guessPrecision(definition)
    if definition.month == nil or (definition.month == 0 and definition.day == 0) then
        return Date.PRECISION.YEAR
    elseif definition.day == nil or definition.day == 0 then
        return Date.PRECISION.MONTH
    elseif definition.hour == nil then
        return Date.PRECISION.DAY
    elseif definition.minute == nil then
        return Date.PRECISION.HOUR
    elseif definition.second == nil then
        return Date.PRECISION.MINUTE
    else
        return Date.PRECISION.SECOND
    end
end

--[[
    Try to find the relevant calendar for a time definition
    @param table time definition
    @return string the calendar name
]]--
local function guessCalendar( definition )
    if definition.year ~= nil and definition.year < 1583 and definition.precision > Date.PRECISION.MONTH then
        return Date.CALENDAR.JULIAN
    else
        return Date.CALENDAR.GREGORIAN
    end
end

--[[
    Parse an ISO 2061 string and return it as a time definition
    @param string iso the iso datetime
    @param boolean withoutRecurrence concider date in the format XX-XX as year-month and not month-day
    @return table
]]--
local function parseIso8601( iso, withoutRecurrence )
    local definition = {}

    --Split date and time
    iso = mw.text.trim( iso:upper() )
    local beginMatch, endMatch, date, time, offset = iso:find( '([%+%-]?[%d%-]+)[T ]?([%d%.:]*)([Z%+%-]?[%d:]*)' )

    if beginMatch ~= 1 or endMatch ~= iso:len() then --iso is not a valid ISO string
        return {}
    end

    --date
    if date ~= nil then
        local isBC = false
        if date:sub( 1, 1 ) == '-' then
            isBC = true
            date = date:sub( 2, date:len() )
        end
        local parts = mw.text.split( date, '-' )
        if not withoutRecurrence and table.maxn( parts ) == 2 and parts[1]:len() == 2 then
            --MM-DD case
            definition.month = tonumber( parts[1] )
            definition.day = tonumber( parts[2] )
        else
            if isBC then
                definition.year = -1 * tonumber( parts[1] )  --FIXME - 1 --Years BC are counted since 0 and not -1
            else
                definition.year = tonumber( parts[1] )
            end
            definition.month = tonumber( parts[2] )
            definition.day = tonumber( parts[3] )
        end
    end

    --time
    if time ~= nil then
        local parts = mw.text.split( time, ':' )
        definition.hour = tonumber( parts[1] )
        definition.minute = tonumber( parts[2] )
        definition.second = tonumber( parts[3] )
    end

    --offset
    if offset ~= nil then
        if offset == 'Z' then
            definition.utcoffset = '+00:00'
        else
            definition.utcoffset = offset
        end
    end

    return definition
end

--[[
    Format UTC offset for ISO output
    @param string offset UTC offset
    @return string UTC offset for ISO
]]--
local function formatUtcOffsetForIso( offset )
    if offset == '+00:00' then
        return 'Z'
    else
        return offset
    end
end

--[[
    Prepend as mutch as needed the character c to the string str in order to to have a string of length length
    @param mixed str
    @param string c
    @param number length
    @return string
]]--
local function prepend(str, c, length)
    str = tostring( str )
    while str:len() < length do
        str = c .. str
    end
    return str
end

--  LEAP_GREGORIAN  --  Is a given year in the Gregorian calendar a leap year ?
local function leapGregorian(year)
    return ((year % 4) == 0) and
            (not (((year % 100) == 0) and ((year % 400) ~= 0)))
end

local isDateInLeapYear = function(indate)
	if indate.calendar == Date.CALENDAR.JULIAN then
		return 0 == indate.year % 4
	end
	return leapGregorian(indate.year)
end

--  GREGORIAN_TO_JD  --  Determine Julian day number from Gregorian calendar date
local GREGORIAN_EPOCH = 1721425.5

local function gregorianToJd(year, month, day)
    return (GREGORIAN_EPOCH - 1) +
           (365 * (year - 1)) +
           math.floor((year - 1) / 4) +
           (-math.floor((year - 1) / 100)) +
           math.floor((year - 1) / 400) +
           math.floor((((367 * month) - 362) / 12) +
           ((month <= 2) and 0 or
                               (leapGregorian(year) and -1 or -2)
           ) +
           day)
end

--  JD_TO_JULIAN  --  Calculate Julian calendar date from Julian day
local function jdToJulian(td)
    local z, a, alpha, b, c, d, e, year, month, day
    
    td = td + 0.5
    z = math.floor(td)
    
    a = z
    b = a + 1524
    c = math.floor((b - 122.1) / 365.25)
    d = math.floor(365.25 * c)
    e = math.floor((b - d) / 30.6001)
    
    month = math.floor((e < 14) and (e - 1) or (e - 13))
    year = math.floor((month > 2) and (c - 4716) or (c - 4715))
    day = b - d - math.floor(30.6001 * e)
    
    --[[
        If year is less than 1, subtract one to convert from
        a zero based date system to the common era system in
        which the year -1 (1 B.C.E) is followed by year 1 (1 C.E.).
    --]]
    
    if year < 1 then
        year = year - 1
    end
    
    return year, month, day
end

--[[ get month name and day (as numeric string), in either order, and return their numbers as day, month
     if optional parameter 'reverse' is true, return month first
]]--
local function monthDay(dateTable, reverse)
	local months = {
		['יאנואר']= 1,
		['פעברואר']= 2,
		['מערץ']= 3,
		['אפריל']= 4,
		['מאי']= 5,
		['יוני']= 6,
		['יולי']= 7,
		['אויגוסט']= 8,
		['סעפטעמבער']= 9,
		['אקטאבער']= 10,
		['נאוועמבער']= 11,
		['דעצעמבער']= 12
	}
	local month = nil
    local day = nil

    for _, value in ipairs(dateTable) do
        if months[value] then
            month = months[value]
        else
            day = tonumber(value)
        end
    end

    if reverse then
        return month, day
    else
        return day, month
    end
end

-- adapted from ro:Modul:GregorianDate
local initialOffset = -3
local limitDates = {
	{year = 4, month = 3, day = 3, calendar = Date.CALENDAR.JULIAN },
	{year = 100, month = 3, day = 2, calendar = Date.CALENDAR.JULIAN },
	{year = 200, month = 3, day = 1, calendar = Date.CALENDAR.JULIAN },
	{year = 300, month = 2, day = 29, calendar = Date.CALENDAR.JULIAN },
	{year = 500, month = 2, day = 28, calendar = Date.CALENDAR.JULIAN },
	{year = 600, month = 2, day = 27, calendar = Date.CALENDAR.JULIAN },
	{year = 700, month = 2, day = 26, calendar = Date.CALENDAR.JULIAN },
	{year = 900, month = 2, day = 25, calendar = Date.CALENDAR.JULIAN },
	{year = 1000, month = 2, day = 24, calendar = Date.CALENDAR.JULIAN },
	{year = 1100, month = 2, day = 23, calendar = Date.CALENDAR.JULIAN },
	{year = 1300, month = 2, day = 22, calendar = Date.CALENDAR.JULIAN },
	{year = 1400, month = 2, day = 21, calendar = Date.CALENDAR.JULIAN },
	{year = 1500, month = 2, day = 20, calendar = Date.CALENDAR.JULIAN },
	{year = 1700, month = 2, day = 19, calendar = Date.CALENDAR.JULIAN },
	{year = 1800, month = 2, day = 18, calendar = Date.CALENDAR.JULIAN },
	{year = 1900, month = 2, day = 17, calendar = Date.CALENDAR.JULIAN },
	{year = 2100, month = 2, day = 16, calendar = Date.CALENDAR.JULIAN },
	{year = 2200, month = 2, day = 15, calendar = Date.CALENDAR.JULIAN },
	{year = 2300, month = 2, day = 14, calendar = Date.CALENDAR.JULIAN }
}

function Date.julianToGregorian(indate)
	if indate.calendar ~= Date.CALENDAR.JULIAN then
			return indate
	end
	
	local outputDate
	if indate.precision > Date.PRECISION.MONTH then
		local offset = initialOffset
		local limitDateIdx = 1
		
		while limitDateIdx < #limitDates and Date.le(limitDates[limitDateIdx], indate) do
			limitDateIdx = limitDateIdx + 1
			offset = offset + 1
		end
	
		outputDate = Date.addDaysToDate(indate, offset)
	else
		outputDate = mw.clone(indate)
	end
	outputDate.calendar = Date.CALENDAR.GREGORIAN
	outputDate.calendarmodel = 'http://www.wikidata.org/entity/Q1985727'
	
	return Date.new(outputDate)
end

function Date.addDaysToDate(indate, days)
	local outdate = mw.clone(indate)
	
	outdate.day = outdate.day + days
	local lastDayOfMonth = maxDaysInMonth[outdate.month]
	while outdate.day > lastDayOfMonth do
		lastDayOfMonth = maxDaysInMonth[outdate.month]
		if outdate.month == 2 and 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

function Date.le(t1, t2, correct_calender)
	if t1.calendar ~= t2.calendar then
		if correct_calender then
			t1 = Date.julianToGregorian(t1)
			t2 = Date.julianToGregorian(t2)
		else
			 error("Calendars don't match", 2)
		end
	end
	if t1.year < t2.year then
		return true
	end
	if t1.year == t2.year then
		if t1.month < t2.month then
			return true
		end
		if t1.month == t2.month and t1.day <= t2.day then
			return true
		end
	end
	return false
end

--Public interface
--[[
    Build a new Date
    @param table definition definition of the time
    @return Date|nil
]]--
function Date.new( definition )
    --Default values
    if definition.precision == nil then
        definition.precision = guessPrecision( definition )
    end
    if definition.calendar == nil then
        definition.calendar = guessCalendar( definition )
    end

    if not validate( definition ) then
        return nil
    end

    local time = {
        year = definition.year or nil,
        month = definition.month or 1,
        day = definition.day or 1,
        hour = definition.hour or 0,
        minute = definition.minute or 0,
        second = definition.second or 0,
        utcoffset = definition.utcoffset or '+00:00',
        calendar = definition.calendar or Date.CALENDAR.GREGORIAN,
        precision = definition.precision or 0
    }

    setmetatable( time, {
        __index = Date,
        __le = le,
        __tostring = function( self ) return self:toString() end
    } )
        
    return time
end

--[[
    Build a new Date from an ISO 8601 datetime
    @param string iso the time as ISO string
    @param boolean withoutRecurrence concider date in the format XX-XX as year-month and not month-day
    @return Date|nil
]]--
function Date.newFromIso8601( iso, withoutRecurrence )
    return Date.new( parseIso8601( iso, withoutRecurrence ) )
end

--[[
    Build a new Date from a Wikidata time value
    @param table wikidataValue the time as represented by Wikidata
    @return Date|nil
]]--
function Date.newFromWikidataValue( wikidataValue )
    local definition = parseIso8601( wikidataValue.time )
    definition.precision = wikidataValue.precision

    if  wikidataValue.calendarmodel == 'http://www.wikidata.org/entity/Q1985727' then
        definition.calendar = Date.CALENDAR.GREGORIAN
    elseif  wikidataValue.calendarmodel == 'http://www.wikidata.org/entity/Q1985786' then
        definition.calendar = Date.CALENDAR.JULIAN
    else
        return nil
    end

    return Date.new( definition )
end

--[[
    Build a new Date from a wiki string
    @param string wikitext string
    @return Date|nil	
]]
function Date.newFromWikitext( wikitext )
	local calendar = nil
	if mw.ustring.find( wikitext, '<small>%(%[%[יוליאנישער קאלענדאר%|יוליאניש%]%]%)</small>' ) then
		calendar = Date.CALENDAR.JULIAN
		wikitext = mw.ustring.gsub( wikitext, "<small>%(%[%[יוליאנישער קאלענדאר%|יוליאניש%]%]%)</small>", "" )
	end
	
	-- Remove instances of [ and ]
	wikitext = mw.ustring.gsub( wikitext, "[%[%]]", "" )
	-- Remove footnotes & directionality markers
	wikitext = mw.text.killMarkers( wikitext )
	wikitext = mw.ustring.gsub(wikitext, "&rlm;","")
	wikitext = mw.ustring.gsub(wikitext, "&lrm;","")
	
	-- BC to minus
	wikitext = mw.ustring.gsub( wikitext, "([0-9]+) לפנה[\"״]ס" , "-%1")

	if mw.ustring.match(wikitext, '^המאה ה[־-]%d+$') then
		local yearStr = mw.ustring.match(wikitext, '^המאה ה[־-](%d+)$')
                return Date.new( { year=tonumber(yearStr)*100, month=0, day=0, precision= Date.PRECISION.YEAR100 } )
	end

	local parts = mw.text.split(mw.text.trim(wikitext),' ')

    local definition = {}
	definition.calendar = calendar
	if #parts==3 then -- DMY date
		definition.year = tonumber(parts[3])
		definition.day, definition.month = monthDay({parts[1], parts[2]})
		assert(definition.year, "Could not recognize year")
		assert(definition.month<13 and definition.month>0, "Could not recognize month number")
		assert(definition.day<32 and definition.day>0, "Wrong date format")
		definition.precision = Date.PRECISION.DAY
	elseif  #parts==2 then -- MY date
		definition.month, definition.year = monthDay(parts, true)
		definition.precision = Date.PRECISION.MONTH
		assert(definition.year<1e7, "Could not recognize year")
		assert(definition.month<13 and definition.month>0, "Could not recognize month number")
	elseif #parts==1 then --Y date
		definition.precision = Date.PRECISION.YEAR
		definition.year=tonumber(parts[1])
		assert(definition.year<1e7, "Could not recognize year")
	else
		error("Unexpected date format")
	end
	
	return Date.new( definition )

end


--[[
    Return a Date as a ISO 8601 string
    @return string
]]--
function Date:toIso8601()
    local iso = ''
    if self.year ~= nil then
        if self.year < 0 then
             --Years BC are counted since 0 and not -1
            iso = '-' .. prepend(string.format('%.0f', -1 * self.year), '0', 4)
        else
            iso = prepend(string.format('%.0f', self.year), '0', 4)
        end
    end

    --month
    if self.precision < Date.PRECISION.MONTH then
        return iso
    end
    if self.iso ~= '' then
        iso = iso .. '-'
    end
    iso = iso .. prepend( self.month, '0', 2 )

    --day
    if self.precision < Date.PRECISION.DAY then
        return iso
    end
    iso = iso .. '-' .. prepend( self.day, '0', 2 )

    --hour
    if self.precision < Date.PRECISION.HOUR then
        return iso
    end
    iso = iso .. 'T' .. prepend( self.hour, '0', 2 )

    --minute
    if self.precision < Date.PRECISION.MINUTE then
        return iso .. formatUtcOffsetForIso( self.utcoffset )
    end
    iso = iso .. ':' .. prepend( self.minute, '0', 2 )

    --second
    if self.precision < Date.PRECISION.SECOND then
        return iso .. formatUtcOffsetForIso( self.utcoffset )
    end
    return iso .. ':' .. prepend( self.second, '0', 2 ) .. formatUtcOffsetForIso( self.utcoffset )
end


--[[
    Return a hebrew representation of Date as a string
    @return string
]]--
function Date:toHebrewString()
	local hebrewStr = ''
	local year = self.year
	
	if (self.precision >= Date.PRECISION.MY100) and (self.precision <= Date.PRECISION.MY) then
		if self.year>0 then
        	return (self.year/1000000) .. ' מיליאן יאר צום ציילונג'
        else
        	return (-self.year/1000000) ..' מיליאן יאר פד"צ'
        end
	elseif (self.precision >=Date.PRECISION.KY100) and (self.precision <= Date.PRECISION.KY) then
		if self.year>0 then
        	return 'האלף ה־'.. (self.year/1000)
        else
        	return 'האלף ה־'.. (-self.year/1000) ..' לפנה״ס'
        end
	elseif self.precision == Date.PRECISION.YEAR100 then
		if year>0 then
	        	return 'המאה ה־'.. math.ceil(self.year/100)
		else
        		return 'המאה ה־'.. math.ceil(-self.year/100) ..' לפנה״ס'
        end
	elseif self.precision == Date.PRECISION.YEAR10 then
        local year = math.floor((self.year < 0 and -1 * self.year or self.year)  / 10) * 10
        if self.year>0 then
        	if year%100==0 then
        		return  'העשור הראשון של המאה ה־'.. tostring((year/100)+1)
        	else
        		return 'שנות ה־' .. tostring(year%100) .. ' של המאה ה־'.. tostring(math.ceil(year/100))
        	end
        else
        	if year%100==0 then
        		return  'העשור הראשון של המאה ה־'.. tostring((year/100))..' לפנה״ס'
        	else
        		return 'שנות ה־' .. tostring(year%100) .. ' של המאה ה־'.. tostring(math.ceil(year/100))..' לפנה״ס'
        	end
        end
	end
    if self.year ~= nil then
        if self.year < 0 then
            hebrewStr = mw.ustring.format('%d לפנה״ס',  (-1*self.year))
        else
			if self.calendar == Date.CALENDAR.JULIAN and self.year > 1583 then
				hebrewStr = mw.ustring.format('%d <small>([[יוליאנישער קאלענדאר|יוליאניש]])</small>',  (self.year))
			else
				hebrewStr = mw.ustring.format('%d',  self.year)
			end
        end
    end

    --month
    if self.precision>=Date.PRECISION.YEAR and self.precision < Date.PRECISION.MONTH then
        return hebrewStr
    end
     local months = { 'יאנואר','פעברואר', 'מערץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אויגוסט', 'סעפטעמבער', 'אקטאבער', 'נאוועמבער','דעצעמבער' }
    hebrewStr = months[self.month] .. ' ' .. hebrewStr

    --day
    if self.precision < Date.PRECISION.DAY then
        return hebrewStr
    end
     hebrewStr = mw.ustring.format('%d %s', self.day, hebrewStr)

    --hour
    if self.precision < Date.PRECISION.HOUR then
        return hebrewStr
    end
    hebrewStr = mw.ustring.format('%s בשעה %d',  hebrewStr, self.hour)

    --minute
    if self.precision < Date.PRECISION.MINUTE then
        return hebrewStr .. formatUtcOffsetForIso( self.utcoffset )
    end
    hebrewStr = hebrewStr .. ':' .. prepend( self.minute, '0', 2 )

    --second
    if self.precision < Date.PRECISION.SECOND then
        return hebrewStr .. formatUtcOffsetForIso( self.utcoffset )
    end
    return hebrewStr .. ':' .. prepend( self.second, '0', 2 ) .. formatUtcOffsetForIso( self.utcoffset )
end

--[[
    Return a Date as a string
    @param mw.language|string|nil language to use. By default the content language.
    @return string
]]--
function Date:toString( language )
    if language == nil then
        return self:toIso8601()
    end
    if language == 'he' then
    	return self:toHebrewString()
    end
    --[[if type( language ) == 'string' then
        language = mw.language.new( language )
    end
    
    return language:formatDate( 'r', self:toIso8601() )]]
    return self:toIso8601()
end

--[[
    Return a Date in HTMl (with a <time> node)
    @param mw.language|string|nil language to use. By default the content language.
    @param table|nil attributes table of attributes to add to the <time> node.
    @return string
]]--
function Date:toHtml( language, attributes )
    if attributes == nil then
        attributes = {}
    end
    attributes['datetime'] = self:toIso8601()
    return mw.text.tag( 'time', attributes, self:toString( language ) )
end

--[[
    All possible precisions for a Date (same ids as Wikibase)
]]--
Date.PRECISION = {
	GY      = 0, --Gigayear
	MY100   = 1, --100 Megayears
	MY10    = 2, --10 Megayears
	MY      = 3, --Megayear
	KY100   = 4, --100 Kiloyears
	KY10    = 5, --10 Kiloyears
	KY      = 6, --Kiloyear
	YEAR100 = 7, --100 years
	YEAR10  = 8, --10 years
	YEAR    = 9,
	MONTH   = 10,
	DAY     = 11,
	HOUR    = 12,
	MINUTE  = 13,
	SECOND  = 14
}

--[[
    Check if the precision is known
    @param number precision ID
    @return boolean
]]--
function Date.knowsPrecision( precision )
	for _,id in pairs( Date.PRECISION ) do
		if id == precision then
			return true
		end
	end
    return false
end

function Date.age(time1, time2)
    if time2 == nil then
        time2 = Date.newFromIso8601(mw.getContentLanguage():formatDate('c', nil, true), true)
    end
	local age = {year, month, day}
	age.year = time2.year - time1.year
	age.month = time2.month - time1.month
	age.day = time2.day - time1.day
	if age.day < 0 then
			local lastMonth = time2.month - 1
			if lastMonth == 0 then
				lastMonth = 12
			end
		age.day = age.day + maxDaysInMonth[lastMonth]
		age.month = age.month - 1
	end
		if age.month < 0 then
		age.month = age.month + 12
		age.year = age.year - 1
	end
    if time1.year < 0 and time2.year > 0 then
        age.year = age.year - 1
    end

	return age
end

function Date:formatDate(options)
    options = options or {}
    local fd = ''
    if self.precision >= Date.PRECISION.DAY then
        fd = self.year < 0 and (-1 * self.year) .. ' פד"צ' or fd .. self.year
        if options.link then fd = '[[' .. fd .. ']]' end
        local d = '2000-' .. prepend(self.month, '0', 2) .. '-' .. prepend(self.day, '0', 2)
        local lang = mw.getContentLanguage()
        fd = fd .. '. ' .. lang:formatDate(options.link and '[[j xg]]' or 'j xg', d)
	elseif self.precision >= Date.PRECISION.MONTH then
		fd = self.year < 0 and (-1 * self.year) .. ' פד"צ' or fd .. self.year
		local month = mw.getContentLanguage():formatDate('F', '2000-' .. self.month)
		if options.link then fd = '[[' .. fd .. ']]' end
		fd = month .. ' ' .. fd
    elseif self.precision >= Date.PRECISION.YEAR then
        fd = self.year < 0 and (-1 * self.year) .. ' פד"צ' or fd .. self.year
        if options.link ~= 'nem' then fd = '[[' .. fd .. ']]' end
    elseif self.precision == Date.PRECISION.YEAR10 then
        local year = math.floor((self.year < 0 and -1 * self.year or self.year)  / 10) * 10
        fd = 'שנות ה-' .. tostring(year%100) .. ' של המאה ה-'.. tostring(ceil(year/100))
        fd = self.year < 0 and year .. ' פד"צ' or tostring(year)
    elseif self.precision == Date.PRECISION.YEAR100 then
        if self.year < 0 then
            fd = 'המאה ה-' .. math.floor(-1 * self.year / 100) .. ' פד"צ'
        else
            fd = 'המאה ה-' ..math.floor(self.year / 100)
        end
        if options.link then fd = '[[' .. fd .. ']]' end
    else
        fd = tostring(self.year)
    end
    
    return fd
end


function parseStrDate(dateStr, dateType)
	local datetime = Date.newFromWikitext( dateStr )
	if datetime.precision >= Date.PRECISION.DAY then -- DMY date
		if dateType=='Y' then
			res = datetime.year
		elseif dateType=='M' then
			res = datetime.month 
		elseif dateType=='D' then
			res = datetime.day
		elseif dateType == 'TS' then
			res = datetime:toIso8601()
		end
	elseif  datetime.precision >= Date.PRECISION.MONTH then -- MY date

		if dateType=='Y' then
			res = datetime.year
		elseif dateType=='M' then
			res = datetime.month
		elseif dateType == 'TS' then
			res = datetime:toIso8601()
		end
	else --Y date
		if dateType=='Y' then
			res = datetime.year
		elseif dateType == 'TS' then
			res = datetime:toIso8601()
		end
	end
	return res
end

function parseDateRange(dateRangeStr, diffFormat, inclusive )
	-- remove footnotes
	dateRangeStr = mw.text.killMarkers(dateRangeStr)
	dateRangeStr = mw.ustring.gsub(dateRangeStr, "&rlm;","")
   
	local outputPrefix = ''
	local parts = mw.text.split(dateRangeStr,' *[–%-] *')
	assert(#parts==2 or #parts==1, "Date range expected format is from - to or from (e.g from - now)")
	
	-- parse dates
	local t1 = Date.newFromWikitext( parts[1] )
	local t2
	local useCurrent = #parts<2 or (parts[2] == 'היום' or parts[2]=='הווה')
	
	if not useCurrent then
		t2 = Date.newFromWikitext( parts[2] )
	else
		t2 = Date.newFromIso8601(mw.getContentLanguage():formatDate('c', nil, true), true)
	end
	
	local hasYears = (diffFormat=='auto')
	local hasDays = (diffFormat=='auto')
	for i=1,#diffFormat do  
		if (diffFormat[i]=='years') then 
			hasYears=true
		elseif diffFormat[i]=='days' then
			hasDays =true
		end 
	end
		
	if hasDays and ((t1.precision>=Date.PRECISION.MONTH and t2.precision<Date.PRECISION.MONTH) or (t1.precision<Date.PRECISION.MONTH and t2.precision>=Date.PRECISION.MONTH)) then
		-- Ambiguous date range
		if t2.year - t1.year > 2 then 
			diffFormat = {'years'}
		else
			return ''
		end
	end
	local NO_GUESS, MONTH_GUESS, DAY_GUESS = 0, 1, 2
	local guessLevel = NO_GUESS
	if t1.precision<Date.PRECISION.MONTH or t2.precision<Date.PRECISION.MONTH then 
		guessLevel = MONTH_GUESS
		inclusive=true
	elseif t1.precision<Date.PRECISION.DAY or t2.precision<Date.PRECISION.DAY then
		guessLevel = DAY_GUESS
	end
	
	local t1 = os.time({
		year = t1.year,
		month = t1.month or 6,
		day = t1.day or 16
		})
	t2= os.time({
			year = t2.year,
			month = t2.month or 6,
			day = t2.day or 16
		})

	local dif = os.difftime (t2, t1)
	local lang = mw.getContentLanguage()
	local readableInterval = lang:getDurationIntervals(dif, {'years', 'days'})
	readableInterval['days'] = readableInterval['days'] or 0
	readableInterval['years'] = readableInterval['years'] or 0
	if not (guessLevel==NO_GUESS) and not (guessLevel==DAY_GUESS and hasYears and #diffFormat==1 and readableInterval['days']>31) then 
		outputPrefix='בערך '
	end
	if inclusive then
		dif = dif+60*60*24 -- include last day
	end

	if diffFormat=="auto" then
		if dif<=60*60*24 then
			return '' -- Ambiguous date range - we arent handling preceision of less than 1 day (hours, minutes, seconds)
		end
		if guessLevel==MONTH_GUESS and readableInterval['years']==0 then
			return '' -- Ambiguous date range
		end
		--for intervals of around year 
		if readableInterval['years']>0 and (readableInterval['days']<30 or (365-readableInterval['days'])<30) then
			-- around
			if readableInterval['days']<30 then
				dif = dif - readableInterval['days']*(60*60*24)
			else
				dif = dif+((365-readableInterval['days'])*(60*60*24))
			end
			diffFormat = {'years'}
		else
			local diffDays = dif/(60*60*24)
			if diffDays<7*3 then diffFormat = { 'days' }
			elseif diffDays<364 then diffFormat = {'weeks', 'days'}
			elseif diffDays<10*365 then diffFormat = {'years', 'weeks'}
			else  diffFormat = {'years'} 
			end
		end
	end
	
	if diffFormat=="raw" then
		return dif
	else
		local res =  outputPrefix..lang:formatDuration(dif, diffFormat)
		-- post process formatDuration which is not good enough
		res= mw.ustring.gsub(res,"כ־([א-ת])","כ%1")
		res= mw.ustring.gsub(res,"וגם ([0-9])","ו־%1")
		res= mw.ustring.gsub(res,"וגם ([א-ת])","ו%1")
		return res
	end
end

function parseDateRangeSafe(frame)
	local diffFormat = 'auto'
	if frame.args[2] == 'ימים' then
		diffFormat = {'days'}
	elseif frame.args[2] == 'יארן' then
		diffFormat = {'years'}
	elseif frame.args[2] == 'שנים וימים' then
		diffFormat = {'years', 'days'}
	elseif frame.args[2] == "הפרש" then
		diffFormat = "raw"
	elseif frame.args[2] == "גיל" then
		diffFormat = {'years'}
	elseif frame.args[2] == "מספר" then
		diffFormat = {'years'}
	end

	local inclusive = (frame.args["כולל"]=="כן")
	local success, res = pcall(parseDateRange, frame.args[1], diffFormat, inclusive)

	if success then
		local str=res
		-- the following translations are needed because the underline function
		-- local format is wierd
		str = mw.ustring.gsub(str,  "(כ)־([א-ת])", "%1%2")

		if frame.args[2] == "גיל" then
			str= mw.ustring.gsub(str,"כ־(.+) שנים","%1 בערך")
			str= mw.ustring.gsub(str," שנים","")
		end
		
	-- This parameter returns the difference as number of years, without any text.
		if frame.args[2] == "מספר" then
			str= mw.ustring.gsub(str,"כ(.+)","%1");
			str= mw.ustring.gsub(str,"־(.+)","%1");
			str= mw.ustring.gsub(str," יאר","")
			if str == "שנתיים" then
				str = 2
			end
			if str == "יאר" then
				str = 1
			end
			str = mw.ustring.gsub(str," שנה", "")
			if tonumber(str) > 0 and
					tonumber(parseDateRange(frame.args[1], "raw", inclusive)) < 0 then
				str = -1 * str
			end
		end
		
		return str
	else
		return frame.args['error'] or '<span class="scribunto-error">דאטום פעלער: '..res..'</span>[[קאטעגאריע:בלעטער מיט דאטום פעלערן]]'
	end
end

function parseStrDateSafe(frame)
	local dateType = frame.args[2]
	if dateType =='יאר' then
		dateType = 'Y'
	elseif dateType=='מאנאט' then
		dateType = 'M'
	elseif dateType=='טאג' then
		dateType='D'
	end

	local success, res = pcall( parseStrDate, frame.args[1], dateType )
	if success then
		if dateType=='Y' and mw.ustring.sub( res, 0, 1)=='-' then
			res = mw.ustring.sub( res, 2).. ' פד"צ'
		end
		return res
	else
		return frame.args['error'] or '<span class="scribunto-error">דאטום פעלער: '..res..'</span>[[קאטעגאריע:בלעטער מיט דאטום פעלערן]]'

	end
end

function linkStrDateUnsafe(frame)
	local dateStr = frame.args[1]
	local linkedDateStr = dateStr
	-- Strip [ and ] chars
	linkedDateStr = mw.ustring.gsub(linkedDateStr, '%[', '')
	linkedDateStr = mw.ustring.gsub(linkedDateStr, '%]', '')
	-- Link D M Y, return [[D M]] [[Y]]
	linkedDateStr = mw.ustring.gsub(linkedDateStr, '^(%d+ %a+) (%d+)$', '[[%1]] [[%2]]')
	-- Link D M, return [[D M]]
	if	mw.ustring.find(linkedDateStr, '%[') == nil	then
		linkedDateStr = mw.ustring.gsub(linkedDateStr, '^(%d+) (%a+)$', '[[%1 %2]]')
	end
	-- Link M Y, return [[M]] [[Y]]
	if	mw.ustring.find(linkedDateStr, '%[') == nil	then
		linkedDateStr = mw.ustring.gsub(linkedDateStr, '^(%a+) (%d+)$', '[[%1]] [[%2]]')
	end
	-- Link Y, return [[Y]]
	if	mw.ustring.find(linkedDateStr, '%[') == nil	then
		linkedDateStr = mw.ustring.gsub(linkedDateStr, '^(%d+)$', '[[%1]]')
	end
	-- Unknown date string format, return the original string
	if	mw.ustring.find(linkedDateStr, '%[') == nil	then
		linkedDateStr = dateStr
	end
	return linkedDateStr
end

Date['רעכן'] = parseStrDateSafe;
Date['רעכן אפשטאנד'] =  parseDateRangeSafe;
Date['parseDateRange'] =  parseDateRange;
Date['מקושר'] = linkStrDateUnsafe;

return Date