--[[--------------- luaHijri---------------------
by hubaishan
v0.5
locate at https://github.com/hubaishan/luaHijri
]]
--[[ ------------ C O N F I G U R A T I O N S ----------------------]]
--[[ As
local adjust_data = { [1443] =
[5] = {2021,12,6},
[7] = 1 }
this means ad just start of month 5 of 1443 to 2021-12-06, and add one day to month 7 of 1443
]]
--local adjust_data = {} --adjust data in separate module
-- Gregorian Calender epoch used only for wertern calendar default value are in Italy some countries may differ
local gregorian_epoch = { jd = 2299161, y = 1582 , m = 10, d = 15}
-- sunday index can be 0 or 1
local wday_sunday = 0
-- day of year (1-1) index
local yday_1_1 = 0
-- @var int 1 to use adjusted Um Al-Qura algorithm, 2 to use Um Al-Qura algorithm, 0 use Hijri Tabular Algorithm
local hijri_mode = mw.loadData('Module:Hijri/Configuration').hijri_mode
--[[ ------------ C O N S T A N T S ----------------]]
-- the Um Al-Qura Calendar start year
local umstartyear = 1318
-- the Um Al-Qura Calendar end year
local umendyear = 1500
-- the Um Al-Qura Calendar start Julian day
local umstartjd = 2415140
-- the Um Al-Qura Calendar end Julian day
local umendjd = 2479960
local UMDATA_COUNT = 2196
--[[ ------------ V A R S ----------------]]
local adjusted_umdata
local raw_umdata
--[[------------------- U T I L T Y U S E F U L F U N C T I O N S------------------------]]
local floor = math.floor
local function round(number)
return floor(number + 0.5)
end
local function in_array( needle, haystack )
if needle == nil then
return false;
end
for n,v in ipairs( haystack ) do
if v == needle then
return n;
end
end
return false;
end
--[[------------------- C A L E N D A R F U N C T I O N S------------------------]]
--[[--- Data ---]]
local function gregorian2jd(year, month, day)
-- from postgresql source
local jd, century
mw.logObject(year,'year')
if year < 1 then
year = year + 1
end
if month > 2 then
month = month + 1
year = year + 4800
else
month = month + 13
year = year + 4799
end
century = floor(year / 100)
return (year * 365 - 32167 + floor(year / 4) - century + floor(century / 4) + floor(7834 * month / 256) + day)
end
-- load Umm al-Qura Calendar Data
-- Modified for MediaWiki
local function get_umdata(mode)
mode = mode or hijri_mode
if mode==2 then
if raw_umdata==nil or #raw_umdata==0 then
raw_umdata = mw.loadData('Module:Hijri/umalqura data')
end
return raw_umdata
elseif mode==1 then
if adjusted_umdata == nil or #adjusted_umdata==0 then
adjusted_umdata = mw.loadData('Module:Hijri/adjusted data')
end
return adjusted_umdata
end
end
--[[--- Hijri ---]]
local function jd2hijri(julianday, mode)
local mjd, ii, n, y, i
local j, j1
local hy, hm, hd, hz
mode = mode or hijri_mode
if (mode>0 and (julianday > umstartjd) and (julianday < umendjd)) then
local umdata = get_umdata(mode)
i = floor((julianday - 1948438) / 29.53056) - ((umstartyear - 1) * 12)
mjd = julianday - 2400000
if i<0 then i = 0 end
for c = i, UMDATA_COUNT do
if (umdata[c] > mjd) then
i = c
break
end
end
ii = floor((i - 1) / 12)
hy = umstartyear + ii
hm = i - 12 * ii
hd = mjd - umdata[i - 1] + 1
hz = mjd - umdata[12 * ii]
else
j = julianday + 7666
n = floor(j / 10631)
j = j - (n * 10631)
j1 = j
y = floor(j / 354.36667)
j = j - round(y * 354.36667)
if j == 0 then
y=y-1
j = j1 - round(y * 354.36667)
hz = j
hd = j - 325
hm = 12
else
hz = j - 1
j = j + 29
hm = floor((24 * j) / 709)
hd = j - floor(((709 * hm) / 24))
end
hy = (n * 30) + y + 1
hy = hy - 5520
if hy <= 0 then
hy = hy - 1
end
end
hz = hz + yday_1_1
return hy, hm, hd, hz
end
local function hijri2jd(hy, hm, hd, mode)
mode = mode or hijri_mode
local ii, i, n
local j
if (mode > 0 and hy >= umstartyear and hy <= umendyear) then
local umdata = get_umdata(mode)
ii = hy - umstartyear
i = hm + 12 * ii
j = hd + umdata[i - 1] - 1
j = j + 2400000
elseif (hy < -5499 or (hy == -5499 and hm < 8) or (hy == -5499 and hm == 8 and hd < 18)) then
j = 0
else
hy = hy < 0 and (hy + 5520) or (hy + 5519)
n = floor(hy / 30)
j = (n * 10631) + round((hy - (n * 30)) * 354.36667)
hm = hm - 1
j = j + (hm * 29) + floor((hm + 1) / 2) + hd
j = j- 7666
end
return j
end
local function hijri_days_in_month(year, month, mode)
mode = mode or hijri_mode
local t = 29
if mode>0 and year >= umstartyear and year <= umendyear then
local i = (year-umstartyear)*12 + month -1
local umdata = get_umdata(mode)
t = umdata[i + 1] - umdata[i]
else
if month == 12 then
if year < 0 then
year = year + 5521
end
if round((year % 30) * 0.36667) > round(((year - 1) % 30) * 0.36667) then
t = 30
end
else
t = 29 + (month % 2);
end
end
return t
end
local function hijri_isleap(year, mode)
mode = mode or hijri_mode
local l
if mode>0 and year >= umstartyear and year <= umendyear then
local umdata = get_umdata(mode)
local ii = year - umstartyear
l = ((umdata[12 * (ii + 1)] - umdata[12 * ii]) > 354) and true or false
else
if year < 0 then
year = year + 5521;
end
l = (round((year % 30) * 0.36667) > round(((year - 1) % 30) * 0.36667)) and true or false
end
return l
end
local function hijri_yday(year,month,day,mode)
local ii
mode = mode or hijri_mode
if (mode>0 and year >= umstartyear and year <= umendyear) then
local umdata = get_umdata(mode)
ii = year - umstartyear
return umdata[(12 * ii) + month -1] - umdata[12 * ii] + day - 1 + yday_1_1
else
return select(month, 0,30,59,89,118,148,177,207,236,266,295,325) + day - 1 + yday_1_1
end
end
local function hijri_check(year,month,day,mode,may_adjusted)
if month < 1 or month > 12 then
return false
end
if day < 1 or day > 30 then
return false
end
if not may_adjusted and day > hijri_days_in_month(year,month,mode) then
return false
end
return true
end
--[[--- Unix and wday---]]
local function jd2unix(jd)
-- 2440588 -- J.D. of 1.1.1970
if jd == nil then
return nil
end
return ((jd - 2440588) * 24 * 3600)
end
local function unix2jd(unix)
if unix == nil then
return nil
end
return floor(unix/(24 * 3600)) + 2440588
end
--returns wday number sunday=wday_sunday
local function jd2wday(jd)
jd = jd + 1
jd = jd % 7
if (jd<0) then
jd = jd + 7
end
return jd + wday_sunday
end
--[[--- Gregorian ---]]
local function jd2gregorian(jd)
-- inspired by postgresql function
local julian, quad, extra, y
local year,month,day
julian = jd + 32044
quad = floor(julian / 146097)
extra = (julian - quad * 146097) * 4 + 3
julian = julian + 60 + quad * 3 + floor(extra / 146097)
quad = floor(julian / 1461)
julian = julian - quad * 1461
y = floor(julian * 4 / 1461)
if (y ~= 0) then
julian = ((julian + 305) % 365) + 123
else
julian = ((julian + 306) % 366) + 123
end
y = y + quad * 4;
year = y - 4800;
quad = floor(julian * 2141 / 65536)
day = julian - floor(7834 * quad / 256)
month = (quad + 10) % 12 + 1
if (year<1) then
year = year-1
end
return year,month,day
end
local function gre_isleap(year)
if year<1 then year=year+1 end
return (year % 4) == 0 and ((year % 100) ~= 0 or (year % 400) == 0)
end
local function gre_yday(year,month,day)
if gre_isleap(year) then
return select(month,0,31,60,91,121,152,182,213,244,274,305,335) + day - 1 + yday_1_1
else
return select(month,0,31,59,90,120,151,181,212,243,273,304,334) + day - 1 + yday_1_1
end
end
local function gre_days_in_month(year, month)
if month == 2 and gre_isleap(year) then
return 29
else
return select(month,31,28,31,30,31,30,31,31,30,31,30,31)
end
end
local function gre_check(year,month,day)
if month < 1 or month > 12 then
return false
end
if day < 1 or day > gre_days_in_month(year,month) then
return false
end
return true
end
--[[--- Julian ---]]
local function jd2julian(jd)
local year, month, day
local temp, dayofYear
temp = jd * 4 + (32083 * 4 - 1)
year = floor(temp / 1461)
dayofYear = floor((temp % 1461) / 4) + 1
temp = dayofYear * 5 - 3
month = floor(temp / 153)
day = floor((temp % 153) / 5) + 1
if (month < 10) then
month = month + 3
else
year = year + 1
month = month - 9
end
year = year - 4800
if (year < 1) then
year = year -1
end
return year, month, day
end
local function julian2jd(year,month,day)
if year<0 then
year = year + 4801
else
year = year + 4800
end
if month > 2 then
month = month -3
else
month = month + 9
year = year - 1
end
return (floor((year * 1461) / 4) + floor((month * 153 + 2) / 5) + day - 32083)
end
local function julian_isleap(year)
if year < 1 then year = year + 1 end
return (year % 4) == 0
end
local function julian_yday(year,month,day)
if julian_isleap(year) then
return select(month,0,31,60,91,121,152,182,213,244,274,305,335) + day - 1 + yday_1_1
else
return select(month,0,31,59,90,120,151,181,212,243,273,304,334) + day - 1 + yday_1_1
end
end
local function julian_days_in_month(year, month)
if month == 2 and julian_isleap(year) then
return 29
else
return select(month,31,28,31,30,31,30,31,31,30,31,30,31)
end
end
local function julian_check(year,month,day)
if month < 1 or month > 12 then
return false
end
if day < 1 or day > julian_days_in_month(year,month) then
return false
end
return true
end
--[[--- Convertion ---]]
-- Return Hijri Date from Gregorian date
local function gregorian2hijri(gyear, gmonth, gday)
return jd2hijri(gregorian2jd(gyear, gmonth, gday))
end
local function hijri2gregorian(hyear, hmonth, hday)
return jd2gregorian(hijri2jd(hyear, hmonth, hday))
end
--[[------------------- D A T E C L A S S ------------------------]]
local datePrototype = {}
-- Set julianDay
function datePrototype:set_jd(arg_jd)
if self.p.type == 'hijri' then
self.p.year, self.p.month, self.p.day, self.p.yday = jd2hijri(arg_jd,self.p.mode)
self.p.jd = arg_jd
elseif self.p.type == 'gregorian' or (self.p.type == 'western' and arg_jd >= gregorian_epoch.jd ) then
self.p.year, self.p.month, self.p.day = jd2gregorian(arg_jd)
self.p.jd, self.p.yday = arg_jd, nil
elseif self.p.type == 'julian' or (self.p.type == 'western' and arg_jd < gregorian_epoch.jd ) then
self.p.year, self.p.month, self.p.day = jd2julian(arg_jd)
self.p.jd, self.p.yday = arg_jd, nil
end
self.p.wday, self.p.month_days, self.p.leap_year = nil, nil, nil
end
function datePrototype:get_jd()
if self.p.jd ~= nil then
return self.p.jd
elseif self.p.year and self.p.month and self.p.day then
self:set_date(self.p.year,self.p.month,self.p.day)
else
local t=os.date("*t")
if self.p.type=='gregorian' then
self.p.year, self.p.month, self.p.day, self.p.wday, self.p.yday = t.year, t.month, t.day, t.wday - 1 + wday_sunday, t.yday
self.p.jd = gregorian2jd(self.p.year, self.p.month, self.p.day)
else
self:from_gregorian(t.year,t.month,t.day)
self.p.wday = t.wday -1 + wday_sunday
end
end
return self.p.jd
end
function datePrototype:set_timestamp(arg_tm)
if self.p.timestamp == arg_tm then
return
end
self.p.timestamp = arg_tm
self:set_jd(unix2jd(arg_tm))
end
-- adjust type of western calendar
local function adj_type(type,year,month,day)
if type == 'western' then
if year>gregorian_epoch.y or (year==gregorian_epoch.y and month>gregorian_epoch.m) or (year==gregorian_epoch.y and month == gregorian_epoch.m and day>=gregorian_epoch.d) then
type='gregorian'
else
type = 'julian'
end
end
return type
end
-- Check the Date
local function check_date(ctype,cmode,year,month,day,may_adjusted)
local type = adj_type(ctype,year,month,day)
if month <1 or month >12 or day <1 or day >31 then
return false
elseif type == 'hijri' then
if day > 30 or (not may_adjusted and day> hijri_days_in_month(year,month,cmode)) then
return false
end
elseif day > select(month,31,29,31,30,31,30,31,31,30,31,30,31) then
return false
elseif month == 2 and day == 29 then
if (((year % 4) ~= 0) or (type == 'gregorian' and (year % 100) == 0 and (year % 400) ~= 0)) then
return false
end
end
return true
end
-- Set Date
-- may_adjusted argument used in Hijri only to accept any 30 day of any month
function datePrototype:set_date(year,month,day,may_adjusted)
if not check_date(self.p.type, self.p.mode, year, month, day, may_adjusted) then
error("Invalid Date")
end
local type=adj_type(self.p.type,year,month,day)
if type == 'hijri' then
self.p.jd = hijri2jd(year,month,day,self.p.mode)
elseif type == 'gregorian' then
self.p.jd = gregorian2jd(year,month,day)
elseif type == 'julian' then
self.p.jd = julian2jd(year,month,day)
end
self.p.year, self.p.month, self.p.day = year,month,day
self.p.wday, self.p.month_days, self.p.leap_year, self.p.yday = nil, nil, nil, nil
end
-- Convert date from hijri
function datePrototype:from_hijri(year,month,day,wday,mode)
mode = mode or self.p.mode
local jd = hijri2jd(year,month,day,mode)
if wday and wday <= 6 and wday >=0 then
local jd_weekday = jd2wday(jd)
local differ = wday - jd_weekday
if differ < - 4 then
jd = jd + (differ + 7)
elseif differ > -3 and differ < 3 then
jd = jd + differ
elseif differ > 4 then
jd = jd + (differ - 7)
else
error("Weekday is very far from Hijri date")
end
end
if self.p.type == 'hijri' and check_date(self.p.type, self.p.mode, year,month,day,true) then
self.p.year,self.p.month,self.p.day,self.p.jd=year,month,day,jd
self.p.wday, self.p.month_days, self.p.leap_year, self.p.yday = nil, nil, nil, nil
else
self:set_jd(jd)
end
end
-- Convert date from Gregorian
function datePrototype:from_gregorian(year,month,day)
self:set_jd(gregorian2jd(year,month,day))
end
-- Convert date from Julian
function datePrototype:from_julian(year,month,day)
self:set_jd(julian2jd(year,month,day))
end
-- Convert date from Western
function datePrototype:from_western(year,month,day)
if year>gregorian_epoch.y or (year==gregorian_epoch.y and month>gregorian_epoch.m) or (year==gregorian_epoch.y and month == gregorian_epoch.m and day>=gregorian_epoch.d) then
self:set_jd(gregorian2jd(year,month,day))
else
self:set_jd(julian2jd(year,month,day))
end
end
-- set type of calendar
function datePrototype:set_type(arg_type)
local new_type,new_mode
arg_type = string.lower(arg_type)
if in_array(arg_type, {'hijri', 'hijri_adjusted_umalqura', 'hijri_umalqura', 'hijri_tabular', 'gregorian', 'julian', 'western'}) then
if string.sub(arg_type,1,5) == 'hijri' then
new_type = 'hijri'
if arg_type == 'hijri_umalqura' then
new_mode = 2
elseif arg_type == 'hijri_tabular' then
new_mode = 0
else
new_mode = 1
end
else
new_type = arg_type
end
if new_type ~= self.p.type or new_mode ~= self.p.mode then
self.p.type = new_type
self.p.mode = new_mode
if self.p.jd then
self:set_jd(self.p.jd)
end
end
else
error('bad calendar type')
end
end
function datePrototype:get_value(key)
if not self.p[key] then
local jd = self:get_jd() -- to init calendar if not
if key=='wday' then
self.p.wday = jd2wday(jd)
elseif key == 'timestamp' then
self.p.timestamp = jd2unix(jd)
else
if self.p.type=='hijri' and key=='leap_year' then
self.p.leap_year = hijri_isleap(self.p.year,self.p.mode)
elseif self.p.type == 'gregorian' or (self.p.type == 'western' and self.p.year > gregorian_epoch.y ) then
self.p.leap_year = gre_isleap(self.p.year)
elseif self.p.type == 'julian' or (self.p.type == 'western' and self.p.year <= gregorian_epoch.y ) then
self.p.leap_year = julian_isleap(self.p.year)
end
if key == 'yday' then
if self.p.type ~= 'hijri' then
if self.p.leap_year then
self.p.yday = select(self.p.month,0,31,60,91,121,152,182,213,244,274,305,335) + self.p.day - 1 + yday_1_1
else
self.p.yday = select(self.p.month,0,31,59,90,120,151,181,212,243,273,304,334) + self.p.day - 1 + yday_1_1
end
elseif self.p.type == 'hijri' then
self.p.yday = hijri_yday(self.p.year,self.p.month,self.p.day,self.p.mode)
end
elseif key == 'month_days' then
if self.p.type=='hijri' then
self.p.month_days = hijri_days_in_month(self.p.year,self.p.month,self.p.mode)
else
if self.p.month == 2 and self.p.leap_year then
self.p.month_days = 29
else
self.p.month_days = select(self.p.month,31,28,31,30,31,30,31,31,30,31,30,31)
end
end
end
end
end
return self.p[key]
end
-- add interval to date
function datePrototype:add(y, m, d)
y,m,d = tonumber(y) or 0,tonumber(m) or 0,tonumber(d) or 0
local jd = self:get_jd()
if y==0 and m == 0 then
jd = jd + d
else
local year,month,day=self.p.year + y, self.p.month + m, self.p.day + d
if self.p.type == 'hijri' then
jd = hijri2jd(year,month,day,self.p.mode)
elseif self.p.type == 'gregorian' then
jd = gregorian2jd(year,month,day)
elseif self.p.type == 'julian' then
jd = julian2jd(year,month,day)
elseif self.p.type == 'western' then
if self.p.jd >= gregorian_epoch.jd then
jd = gregorian2jd(year,month,day)
if jd< gregorian_epoch.jd then
jd = julian2jd(year,month,day)
end
else
jd = julian2jd(year,month,day)
if jd>= gregorian_epoch.jd then
jd = gregorian2jd(year,month,day)
end
end
end
end
if jd ~= self.p.jd then
self:set_jd(jd)
end
end
function datePrototype:iso()
local ret, str, jd = '', '', self:get_jd()
local year, month, day
if self.p.type=='gregorian' then
year, month, day = self.p.year, self.p.month, self.p.day
else
year, month, day = jd2gregorian(jd)
end
if year < 0 then
year = year - 1
ret = '-'
end
str = tostring(math.abs(year))
return ret .. string.rep('0', 4 - #str) .. str .. '-' .. ((month<10) and '0' or '') .. month .. '-' .. ((day<10) and '0' or '') .. day
end
local function DateWrapper(dt)
local t = {}
t.p = {}
local rop = {
type = false,
mode = true,
jd = false,
timestamp = false,
year = true,
month = true,
day = true,
wday = true,
yday = true,
month_days = true,
leap_year = true
}
local mt = {
__index = function ( tt, k )
assert( t == tt )
local v = tt.p[k] or datePrototype[k]
if v == nil and rop[k] ~=nil then
v = tt:get_value(k)
end
return v
end,
__newindex = function ( t, k, v )
if rop[k] then
error("Attempt to modify read-only property")
elseif k == 'jd' then
t:set_jd(v)
elseif k == 'type' then
t:set_type(v)
elseif k == 'timestamp' then
t:set_timestamp(v)
else
rawset(t, k, v)
end
end
}
-- This is just to make setmetatable() fail
mt.__metatable = mt
return setmetatable( t, mt )
end
local function Date(arg_type, arg_jd, arg_month, arg_day)
local d = DateWrapper({})
-- process argument
d:set_type(arg_type)
if arg_jd and arg_month and arg_day then
d:set_date(arg_jd, arg_month, arg_day)
elseif arg_jd then
d:set_jd(arg_jd)
end
return d
end
return {
gregorian2jd = gregorian2jd,
jd2hijri = jd2hijri,
hijri2jd = hijri2jd,
hijri_days_in_month = hijri_days_in_month,
hijri_isleap = hijri_isleap,
hijri_yday = hijri_yday,
jd2unix = jd2unix,
unix2jd = unix2jd,
jd2wday = jd2wday,
jd2gregorian = jd2gregorian,
gre_isleap = gre_isleap,
gre_yday = gre_yday,
gre_days_in_month = gre_days_in_month,
jd2julian = jd2julian,
julian2jd = julian2jd,
julian_isleap = julian_isleap,
julian_yday = julian_yday,
julian_days_in_month = julian_days_in_month,
gregorian2hijri = gregorian2hijri,
hijri2gregorian = hijri2gregorian,
Date = Date,
hijri_check = hijri_check,
gre_check = gre_check,
julian_check = julian_check
}