-- This module is under development.
-- A library of Iranian calendar functions
-- Written by Alireza Eskandarpour Shoferi
-- Based on JalaliJSCalendar (by Ali Farhadi) and jdf (by Reza Gholampanahi)
--
-- Distributed under the terms of the CC BY-SA 4.0
-- load necessary modules
local data = require("Module:Iranian calendar/library/data")
local config = require("Module:Iranian calendar/library/config")
local formatNum = require("Module:Numeral converter").convert
local getArgs = require("Module:Arguments").getArgs
local p = {}
-- Used for calculating hour, seconds and minutes in Asia/Tehran timezone
local mw_en = mw.getLanguage("en")
local mw_fa = mw.getContentLanguage()
function p.getCurrentJalaliDateInTable()
local date = os.date("!*t")
date.hour = mw_en:formatDate('G', "+3 hours +30 minutes")
date.sec = mw_en:formatDate('s', "+3 hours +30 minutes")
date.min = mw_en:formatDate('i', "+3 hours +30 minutes")
return p.toJalali(date)
end
function p.isKabise(frame)
local args = getArgs(frame, {trim = true, removeBlanks = true})
-- Giving the year argument in two ways
args.year = formatNum("en", args[1] or args.year)
return p._isKabise(args)
end
function p._isKabise(args)
if args.year % 33 % 4 - 1 == math.floor(args.year % 33 * .05) then
return 1
else
return 0
end
end
-- givenDate argument should be a table of known keys (year, month, day, hour, min, and sec)
local function formatting(format, date, lang)
local chars = {}
for c in mw.ustring.gmatch(format,"%%%S") do
-- If the special character is not exists already
if type(chars[c]) == "nil" then
chars[c] = ""
end
end
local notSupportedChars = {}
local isKabise = p.isKabise(date)
-- To get the ISO day of the year 1 added (January 1 = 0).
local dayOfYear = mw_en:formatDate("z", "+3 hours +30 minutes") + 1
-- Following special characters and their expressions borrowed from http://jdf.scr.ir
for k,_ in pairs(chars) do
if k == "%a" then
if tonumber(date.hour) < 12 then
chars[k] = config.selectPhrase("abbrClock", 1)
else
chars[k] = config.selectPhrase("abbrClock", 2)
end
elseif k == "%b" then
chars[k] = math.floor(date.month / 3.1 + 1)
elseif k == "%c" then
-- os.date("!%z") may not return what I need (not tested)
chars[k] = date.year .. "/" .. date.month .. "/" .. date.day .. "،" .. date.hour .. ":" .. date.min .. ":" .. date.sec .. " " .. os.date("!%z")
elseif k == "%d" then
if date.day < 10 then
chars[k] = "0" .. date.day
else
chars[k] = date.day
end
elseif k == "%f" then
-- Key of tables in Lua starts from 1, so 1 added
chars[k] = config.selectPhrase("seasons", math.floor((date.month / 3.1)) + 1)
elseif k == "%g" then
chars[k] = mw_en:formatDate("g", "+3 hours +30 minutes")
elseif k == "%h" then
chars[k] = mw_en:formatDate("h", "+3 hours +30 minutes")
elseif k == "%i" then
chars[k] = date.min
elseif k == "%j" then
chars[k] = mw_fa:formatDate("xij", "+3 hours +30 minutes")
elseif k == "%k" then
chars[k] = math.floor(100 - (dayOfYear / (p.isKabise(date) + 365) * 1000) / 10)
elseif k == "%l" then
-- Key of tables in Lua starts from 1, so 1 added
chars[k] = config.selectPhrase("daysWeek", mw_en:formatDate("w", "+3 hours +30 minutes") + 1)
elseif k == "%m" then
if date.month > 9 then
chars[k] = date.month
else
chars[k] = string.gsub(date.month,"([^0])","0%1")
end
elseif k == "%n" then
chars[k] = mw_fa:formatDate("xin", "+3 hours +30 minutes")
elseif k == "%o" then
chars[k] = mw_fa:formatDate("xiY", "+3 hours +30 minutes")
--[[local jdw
if date.wday == 6 then
jdw = 0
else
jdw = date.wday + 1
end
local dny = 364 + p.isKabise(date) - dayOfYear
if (jdw > (dayOfYear + 3) and dayOfYear < 3) then
chars[k] = date.year - 1
else
if (3-dny) > jdw and dny < 3 then
chars[k] = date.year + 1
else
chars[k] = date.year
end
end]]--
elseif k == "%p" then
chars[k] = config.selectPhrase("astrologicalSign", date.month)
elseif k == "%q" then
chars[k] = config.selectPhrase("astrologicalSign", (date.year % 12))
elseif k == "%r" then
-- inja
elseif k == "%s" then
chars[k] = date.sec
elseif k == "%t" then
if date.month ~= 12 then
chars[k] = math.floor(31 - date.month / 6.5)
else
chars[k] = p.isKabise(date.year) + 29
end
elseif k == "%v" then
local sl = 2
local num = string.sub(date.year, 3)
local xy3 = string.sub(num, 2 - sl, 1)
local h3 = ""
local h34 = ""
local h4 = ""
local p34
if xy3 == 1 then
p34 = ""
h34 = config.selectPhrase("k34", string.sub(num, 2 - sl, 2) - 10)
else
local xy4 = string.sub(num, 3 - sl, 1)
if xy3 == 0 or xy4 == 0 then
p34 = ""
else
p34 = config.selectPhrase("andWord")
end
h3 = config.selectPhrase("k3", xy3)
h4 = config.selectPhrase("k4", xy4)
end
if num > 99 then
local numbs = {"12", "13", "14", "19", "20"}
for i = 1, 5 do
num = string.gsub(string.sub(date.year, 1, 2), numbs[i], config.selectPhrase("thousands", i))
if string.sub(date.year, 3) == "00" then
num = num .. h3 .. p34 .. h34 .. h4
else
num = num .. config.selectPhrase("andWord") .. h3 .. p34 .. h34 .. h4
end
end
chars[k] = num
else
chars[k] = ""
end
elseif k == "%w" then
if date.wday == 6 then
chars[k] = 0
else
chars[k] = date.wday + 1
end
elseif k == "%y" then
chars[k] = string.sub(date.year, 3)
elseif k == "%z" then
chars[k] = dayOfYear
elseif k == "%A" then
if tonumber(date.hour) < 12 then
chars[k] = config.selectPhrase("clock", 1)
else
chars[k] = config.selectPhrase("clock", 2)
end
elseif k == "%D" then
chars[k] = config.selectPhrase("abbrDaysWeekOneCharacter", (tonumber(date.wday)))
elseif k == "%F" then
chars[k] = config.selectPhrase("months", tonumber(date.month))
elseif k == "%G" then
chars[k] = date.hour
elseif k == "%H" then
chars[k] = mw_en:formatDate("H", "+3 hours +30 minutes")
elseif k == "%J" then
chars[k] = config.selectPhrase("daysMonthNumberWord", tonumber(date.day))
elseif k == "%K" then
chars[k] = math.floor((dayOfYear/(p.isKabise(date) + 365) * 1000) / 10)
elseif k == "%L" then
chars[k] = p.isKabise(date)
elseif k == "%M" then
chars[k] = config.selectPhrase("abbrMonths", tonumber(date.month))
elseif k == "%N" then
chars[k] = tonumber(date.wday)
elseif k == "%O" then
chars[k] = os.date("!%z")
elseif k == "%Q" then
chars[k] = isKabise + 364 - dayOfYear
elseif k == "%S" then
chars[k] = config.selectPhrase("suffix")
elseif k == "%V" then
local sl = 4
local num = date.year
local xy3 = string.sub(num, 2 - sl, 1)
local h3 = ""
local h34 = ""
local h4 = ""
local p34
if xy3 == 1 then
p34 = ""
h34 = config.selectPhrase("k34", string.sub(num, 2 - sl, 2) - 10)
else
local xy4 = string.sub(num, 3 - sl, 1)
if xy3 == 0 or xy4 == 0 then
p34 = ""
else
p34 = config.selectPhrase("andWord")
end
h3 = config.selectPhrase("k3", xy3)
h4 = config.selectPhrase("k4", xy4)
end
if num > 99 then
local numbs = {"12", "13", "14", "19", "20"}
for i = 1, 5 do
num = string.gsub(string.sub(date.year, 1, 2), numbs[i], config.selectPhrase("thousands", i))
if string.sub(date.year, 3) == "00" then
num = num .. h3 .. p34 .. h34 .. h4
else
num = num .. config.selectPhrase("andWord") .. h3 .. p34 .. h34 .. h4
end
end
chars[k] = num
else
chars[k] = ""
end
elseif k == "%W" then
local avs
if date.wday == 6 then
avs = 0
else
avs = date.wday + 1
end
avs = avs - dayOfYear % 7
if avs < 0 then
avs = avs + 7
end
local num = math.floor((dayOfYear + avs) / 7)
if avs < 4 then
num = num + 1
elseif num < 1 then
if avs == 4 then
num = 53
elseif date.year % 33 % 4 - 2 == math.floor(date.year % 33 * .05) then
if avs == 5 then
num = 53
end
elseif date.year % 33 % 4 - 2 ~= math.floor(date.year % 33 * .05) then
if avs == 4 then
num = 52
end
else
num = 52
end
end
local aks = avs + isKabise
if aks == 7 then
aks = 0
end
if(isKabise + 363 - dayOfYear) < aks and aks < 3 then
chars[k] = "01"
else
if (num < 10) then
chars[k] = "0" .. num
else
chars[k] = num
end
end
-- The given special character is not supported
else
table.insert(notSupportedChars, k)
end
end
if #notSupportedChars > 0 then
error(string.format(config.selectPhrase("errors", "notSupportedCharacters", lang), table.concat(notSupportedChars, ", ")))
else
for k,v in pairs(chars) do
format = mw.ustring.gsub(format, "%"..k, v)
end
return format
end
end
function p.toGregorian(frame)
local args = getArgs(frame, {trim = true, removeBlanks = true})
args.year = tonumber(formatNum("en", args[1] or args.year)) - 979
args.month = tonumber(formatNum("en", args[2] or args.month)) - 1
args.day = tonumber(formatNum("en", args[3] or args.day)) - 1
return p._toGregorian(args)
end
function p._toGregorian(args)
local jalDayNum = 365 * args.year + math.floor(args.year / 33) * 8 + math.floor((args.year % 33 + 3) / 4)
local i = 0
while i < args.month do
i = i + 1
jalDayNum = jalDayNum + data.daysMonth.jalali[i]
end
jalDayNum = jalDayNum + args.day
local greDayNum = jalDayNum + 79
-- 146097 = 365*400 + 400/4 - 400/100 + 400/400
local greYear = 1600 + 400 * math.floor(greDayNum / 146097)
greDayNum = greDayNum % 146097
local leap = true
-- 36525 = 365*100 + 100/4
if (greDayNum >= 36525) then
greDayNum = greDayNum - 1
-- 36524 = 365*100 + 100/4 - 100/100
greYear = greYear + 100 * math.floor(greDayNum / 36524)
greDayNum = greDayNum % 36524
if (greDayNum >= 365) then
greDayNum = greDayNum + 1
else
leap = false
end
end
-- 1461 = 365*4 + 4/4
greYear = greYear + 4 * math.floor(greDayNum / 1461)
greDayNum = greDayNum % 1461
if (greDayNum >= 366) then
leap = false
greDayNum = greDayNum - 1
greYear = greYear + math.floor(greDayNum / 365)
greDayNum = greDayNum % 365
end
i = 0
while greDayNum >= data.daysMonth.gregorian[i+1] + ((i == 1 and leap) and 1 or 0) do
greDayNum = greDayNum - data.daysMonth.gregorian[i+1] + ((i == 1 and leap) and 1 or 0)
i = i + 1
end
return {year = greYear, month = i+1, day = greDayNum+1}
end
function p.checkDate(frame)
local args = getArgs(frame, {trim = true, removeBlanks = true})
args.year = tonumber(formatNum("en", args[1]))
args.month = tonumber(formatNum("en", args[2]))
args.day = tonumber(formatNum("en", args[3]))
return p._checkDate(args)
end
function p._checkDate(args)
return not(args.year < 0 or args.year > 32767 or args.month < 1 or args.month > 12 or args.day < 1 or args.day >
((data.daysMonth.jalali[args.month] or 0) + ((args.month == 12 and not(((args.year - 979) % 33 % 4) and 1 or 0)) and 1 or 0)))
end
function p.toJalali(frame)
local args = getArgs(frame, {trim = true, removeBlanks = true})
args.year = tonumber(formatNum("en", args[1] or args.year)) - 1600
args.month = tonumber(formatNum("en", args[2] or args.month)) - 1
args.day = tonumber(formatNum("en", args[3] or args.day)) - 1
return p._toJalali(args)
end
function p._toJalali(args)
local greDayNum = 365 * args.year + math.floor((args.year + 3) / 4) - math.floor((args.year + 99) / 100) + math.floor((args.year + 399) / 400)
local i = 0
while i <= args.month do
i = i + 1
greDayNum = greDayNum + data.daysMonth.gregorian[i]
end
if (args.month > 1 and ((args.year % 4 == 0 and args.year % 100 ~= 0) or (args.year % 400 == 0))) then
-- leap and after Feb
greDayNum = greDayNum + 1
end
greDayNum = greDayNum + args.day
local jalDayNum = greDayNum - 79
local j_np = math.floor(jalDayNum / 12053)
jalDayNum = jalDayNum % 12053
local jalaliYear = 979 + 33 * j_np + 4 * math.floor(jalDayNum / 1461)
jalDayNum = jalDayNum % 1461
if (jalDayNum >= 366) then
jalaliYear = jalaliYear + math.floor((jalDayNum - 1) / 365)
jalDayNum = (jalDayNum - 1) % 365
end
i = 0
while i <= 11 and jalDayNum > data.daysMonth.jalali[i+1] do
i = i + 1
jalDayNum = jalDayNum - data.daysMonth.jalali[i]
end
args.year = jalaliYear
args.month = i
args.day = jalDayNum + 1
return args
end
function p.getCurrentJalaliYear(frame)
local args = getArgs(frame, {trim = true, removeBlanks = true})
args.lang = args.lang or config.defaultLang
return p._getCurrentJalaliYear(args)
end
function p._getCurrentJalaliYear(args)
return formatNum(args.lang, p.getCurrentJalaliDateInTable().year)
end
function p.getCurrentJalaliMonth(frame)
local args = getArgs(frame, {trim = true, removeBlanks = true})
args.lang = args.lang or config.defaultLang
return p._getCurrentJalaliMonth(args)
end
function p._getCurrentJalaliMonth(args)
return formatNum(args.lang, p.getCurrentJalaliDateInTable().month)
end
function p.getFormattedCurrentJalaliDate(frame)
local args = getArgs(frame, {trim = true, removeBlanks = true})
args.lang = args.lang or config.defaultLang
args.format = args.format or config.defaultFormat
return p._getFormattedCurrentJalaliDate(args)
end
function p._getFormattedCurrentJalaliDate(args)
return formatNum(args.lang, formatting(args.format, p.getCurrentJalaliDateInTable(), args.lang))
end
function p.getCurrentJalaliDay(frame)
local args = getArgs(frame, {trim = true, removeBlanks = true})
args.lang = args.lang or config.defaultLang
return p._getCurrentJalaliDay(args)
end
function p._getCurrentJalaliDay(args)
return formatNum(args.lang, p.getCurrentJalaliDateInTable().day)
end
return p