יחידה:תאריך

מתוך ויקיספר, אוסף הספרים והמדריכים החופשי

ניתן ליצור תיעוד על היחידה הזאת בדף יחידה:תאריך/תיעוד

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

    --ofset
    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

-- 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("Calanders dont 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 months = {
		['ינואר']= 1,
		['פברואר']= 2,
		['מרץ']= 3,
		['אפריל']= 4,
		['מאי']= 5,
		['יוני']= 6,
		['יולי']= 7,
		['אוגוסט']= 8,
		['ספטמבר']= 9,
		['אוקטובר']= 10,
		['נובמבר']= 11,
		['דצמבר']= 12
	}
	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;","")
	mw.log('תאריך' .. ":" .. wikitext)
	-- BC to minus
	wikitext = mw.ustring.gsub( wikitext, "([0-9]+) לפנה[\"״]ס" , "-%1")
	
	for a in pairs(months) do
		wikitext = mw.ustring.gsub(wikitext, ' ?ב?'..a, ' ' .. months[a]) 
	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 definition = {}
	definition.calendar = calendar
	if #parts==3 then -- DMY date
		definition.year = tonumber(parts[3])
		definition.month = tonumber(parts[2])
		definition.day = tonumber(parts[1])
		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.year = tonumber(parts[2])
		definition.month = tonumber(parts[1])  
		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 = time2.year - time1.year
    if time2.month < time1.month or (time2.month == time1.month and time2.day < time1.day) then
        age = age - 1
    end
    if time1.year < 0 and time2.year > 0 then
        age = age - 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;","")
   mw.log("טווח תאריכים:" .. dateRangeStr)
	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
	if #parts==2 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
		return '' -- Ambiguous date range
	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
		return outputPrefix..lang:formatDuration(dif, diffFormat)
	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 = "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")
		str= mw.ustring.gsub(str,"וגם ([0-9])","ו-%1")
		str= mw.ustring.gsub(str,"וגם ([א-ת])","ו%1");

		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
			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

Date['חשב'] = parseStrDateSafe;
Date['חשב טווח'] =  parseDateRangeSafe;
Date['parseDateRange'] =  parseDateRange;

return Date