Module:BENGALIDATE/sandbox

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

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.