Module:BENGALIDATE/sandbox
Appearance
| This is the module sandbox page for Module:BENGALIDATE (diff). |
| This module depends on the following other modules: |
Usage
[edit]{{BENGALIDATE}} gives current date based on the revised Bengali calendar (which is officially adopted for use in Bangladesh), and gets automatically updated everyday past mid-night Bangladesh time. Use {{BENGALIYEAR}} also to show Bengali Era (BS).
</noinclude>
local p = {}
local getArgs = require('Module:Arguments').getArgs
local epoch_module = require('Module:Bengali Calendar Epoch Offset')
local timestamp = epoch_module._main
local BENGALI_UNIX_EPOCH = epoch_module.EPOCH
local EPOCH_YEAR = 1432
local SECONDS_PER_DAY = 86400
local DAY_START_OFFSET = 6 * 3600
local DEFAULT_FORMAT = "[[F]] j, Y"
local BN_MONTH = {
{ name = "Boisakh", link = "Boisakh", abbr = "Boi" },
{ name = "Joishtho", link = "Joishtho", abbr = "Joi" },
{ name = "Asharh", link = "Asharh", abbr = "Ash" },
{ name = "Shrabon", link = "Shrabon", abbr = "Shr" },
{ name = "Bhadro", link = "Bhadro", abbr = "Bha" },
{ name = "Ashshin", link = "Ashshin", abbr = "Ash" },
{ name = "Kartik", link = "Kartik (month)", abbr = "Kar" },
{ name = "Ogrohayon", link = "Ogrohayon", abbr = "Ogr" },
{ name = "Poush", link = "Poush", abbr = "Pou" },
{ name = "Magh", link = "Magh (Bengali calendar)", abbr = "Mag" },
{ name = "Falgun", link = "Falgun", abbr = "Fal" },
{ name = "Chaitro", link = "Chaitro", abbr = "Cha" },
}
local BN_WEEKDAY = {
{ name = "Robibar", abbr = "Rob" },
{ name = "Sombar", abbr = "Som" },
{ name = "Mongolbar", abbr = "Mon" },
{ name = "Budhbar", abbr = "Bud" },
{ name = "Brihoshpotibar", abbr = "Bri" },
{ name = "Shukrobar", abbr = "Shu" },
{ name = "Shonibar", abbr = "Sho" },
}
local MONTH_LENGTHS_NORMAL = { 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 }
local MONTH_LENGTHS_LEAP = { 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 30 }
local function isLeapYear(year)
local n = year - 593
return (n % 400 == 0) or (n % 100 ~= 0 and n % 4 == 0)
end
local function getMonthLengths(year)
return isLeapYear(year) and MONTH_LENGTHS_LEAP or MONTH_LENGTHS_NORMAL
end
local function daysToDate(days)
local year = EPOCH_YEAR
if days >= 0 then
while true do
local year_len = isLeapYear(year) and 366 or 365
if days < year_len then break end
days = days - year_len
year = year + 1
end
else
repeat
year = year - 1
local year_len = isLeapYear(year) and 366 or 365
days = days + year_len
until days >= 0
end
local lengths = getMonthLengths(year)
local month = 1
local day_of_year = days
while month < 12 and days >= lengths[month] do
days = days - lengths[month]
month = month + 1
end
return year, month, days + 1, day_of_year
end
local function tsToWeekday(ts)
local day_index = math.floor(ts / SECONDS_PER_DAY)
return ((day_index + 4) % 7) + 1
end
local function isoWeekNumber(ts)
local day_index = math.floor(ts / SECONDS_PER_DAY)
local dow = (day_index + 3) % 7
return math.floor((day_index - dow + 10) / 7)
end
-- ISO week year: if week 1 belongs to next year or week belongs to prev year, adjust
local function isoWeekYear(year, month_idx, day, week)
if week >= 52 and month_idx == 1 and day <= 3 then
return year - 1
elseif week == 1 and month_idx == 12 and day >= 29 then
return year + 1
end
return year
end
local function tsToTime(ts)
local secs = ts % SECONDS_PER_DAY
if secs < 0 then secs = secs + SECONDS_PER_DAY end
return math.floor(secs / 3600), math.floor((secs % 3600) / 60), secs % 60
end
local function buildCtx(ts)
local date_ts = ts - DAY_START_OFFSET
local days = math.floor(date_ts / SECONDS_PER_DAY)
local year, month_idx, day, day_of_year = daysToDate(days)
local hour, min, sec = tsToTime(ts)
local lengths = getMonthLengths(year)
local week = isoWeekNumber(date_ts)
return {
day = day,
month_idx = month_idx,
month_entry = BN_MONTH[month_idx],
year = year,
hour = hour,
min = min,
sec = sec,
weekday = tsToWeekday(date_ts),
week = week,
week_year = isoWeekYear(year, month_idx, day, week),
day_of_year = day_of_year,
month_days = lengths[month_idx],
is_leap = isLeapYear(year),
ts = ts,
}
end
local TOKEN = {
-- year
Y = function(c) return tostring(c.year) end,
y = function(c) return string.format("%02d", c.year % 100) end,
L = function(c) return c.is_leap and "1" or "0" end,
o = function(c) return tostring(c.week_year) end,
-- month
n = function(c) return tostring(c.month_idx) end,
m = function(c) return string.format("%02d", c.month_idx) end,
M = function(c) return c.month_entry.abbr end,
F = function(c) return c.month_entry.name end,
t = function(c) return tostring(c.month_days) end,
-- day
j = function(c) return tostring(c.day) end,
d = function(c) return string.format("%02d", c.day) end,
z = function(c) return tostring(c.day_of_year) end,
-- week / weekday
W = function(c) return string.format("%02d", c.week) end,
N = function(c)
-- ISO: Mon=1 Sun=7; our table Sun=1 Sat=7
return tostring(c.weekday == 1 and 7 or c.weekday - 1)
end,
w = function(c)
-- Sun=0 Sat=6
return tostring(c.weekday - 1)
end,
D = function(c) return BN_WEEKDAY[c.weekday].abbr end,
l = function(c) return BN_WEEKDAY[c.weekday].name end,
-- hour
a = function(c) return c.hour < 12 and "am" or "pm" end,
A = function(c) return c.hour < 12 and "AM" or "PM" end,
g = function(c) return tostring(c.hour % 12 == 0 and 12 or c.hour % 12) end,
h = function(c) return string.format("%02d", c.hour % 12 == 0 and 12 or c.hour % 12) end,
G = function(c) return tostring(c.hour) end,
H = function(c) return string.format("%02d", c.hour) end,
-- minutes and seconds
i = function(c) return string.format("%02d", c.min) end,
s = function(c) return string.format("%02d", c.sec) end,
-- unix
U = function(c) return tostring(c.ts + BENGALI_UNIX_EPOCH) end,
u = function(c) return tostring(c.ts) end,
-- timezone (static, always Bangladesh)
e = function(_) return "Asia/Dhaka" end,
I = function(_) return "0" end,
O = function(_) return "+0600" end,
P = function(_) return "+06:00" end,
T = function(_) return "BST" end,
Z = function(_) return "21600" end,
}
local function expandToken(char, ctx)
local fn = TOKEN[char]
return fn and fn(ctx) or char
end
local function wrapInLink(char, expanded, month_entry)
if (char == "F" or char == "M") and month_entry.link ~= month_entry.name then
return "[[" .. month_entry.link .. "|" .. month_entry.name .. "]]"
else
return "[[" .. expanded .. "]]"
end
end
local function formatDate(ctx, format)
local result = {}
local i = 1
while i <= #format do
local two = format:sub(i, i + 1)
if two == "''" then
i = i + 2
while i <= #format do
if format:sub(i, i + 1) == "''" then i = i + 2; break end
table.insert(result, format:sub(i, i))
i = i + 1
end
elseif format:sub(i, i) == '"' then
i = i + 1
while i <= #format do
if format:sub(i, i) == '"' then i = i + 1; break end
table.insert(result, format:sub(i, i))
i = i + 1
end
elseif two == "[[" then
i = i + 2
local linked = {}
while i <= #format and format:sub(i, i + 1) ~= "]]" do
local char = format:sub(i, i)
table.insert(linked, { char = char, expanded = expandToken(char, ctx) })
i = i + 1
end
i = i + 2
if #linked == 1 then
table.insert(result, wrapInLink(linked[1].char, linked[1].expanded, ctx.month_entry))
else
local inner = {}
for _, t in ipairs(linked) do table.insert(inner, t.expanded) end
table.insert(result, "[[" .. table.concat(inner) .. "]]")
end
else
table.insert(result, expandToken(format:sub(i, i), ctx))
i = i + 1
end
end
return table.concat(result)
end
local function isDateString(s)
return s:match("^%d+%-%d+%-%d+$") or s:match("^%d+%s+%a+%s+%d+$")
end
local function resolveTimestamp(date_str)
local raw, err = date_str and timestamp(date_str) or timestamp()
if not raw then
return nil, err or "Invalid date: " .. tostring(date_str)
end
return tonumber(raw), nil
end
local function parseArgs(args)
local first = args[1] or ""
if first == "" then
return { date_str = nil, format = DEFAULT_FORMAT }
elseif isDateString(first) then
return { date_str = first, format = args[2] or DEFAULT_FORMAT }
else
return { date_str = args[2], format = first }
end
end
function p.main(frame)
local args = getArgs(frame)
local parsed = parseArgs(args)
local ts, err = resolveTimestamp(parsed.date_str)
if err then
return '<span class="error">' .. err .. '</span>'
end
return formatDate(buildCtx(ts), parsed.format)
end
return p