跳转到内容

模組:沙盒/a2569875/PythonScript/Librarys/Datetime

维基百科,自由的百科全书
-- Module:PythonScript/Librarys/Datetime
local DatetimeMod = {}
local PyClass = require("Module:沙盒/a2569875/PythonScript/Class")
local Runtime = require("Module:沙盒/a2569875/PythonScript/Runtime")

-- ==========================================
-- 內部輔助:純 Lua 公曆引擎與比較器
-- ==========================================
local _DAYS_BEFORE_MONTH = {
    {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
    {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
}

local function is_leap(year) return (year % 4 == 0 and (year % 100 ~= 0 or year % 400 == 0)) and 2 or 1 end

local function ymd2ord(year, month, day)
    local dim = _DAYS_BEFORE_MONTH[is_leap(year)][month]
    local y = year - 1
    return y * 365 + math.floor(y / 4) - math.floor(y / 100) + math.floor(y / 400) + dim + day
end

local function ord2ymd(n)
    n = n - 1
    local n400 = math.floor(n / 146097); n = n % 146097
    local year = n400 * 400 + 1
    local n100 = math.floor(n / 36524); if n100 == 4 then n100 = 3; n = 36524 else n = n % 36524 end
    year = year + n100 * 100
    local n4 = math.floor(n / 1461); n = n % 1461
    year = year + n4 * 4
    local n1 = math.floor(n / 365); if n1 == 4 then n1 = 3; n = 365 else n = n % 365 end
    year = year + n1
    local leap = is_leap(year)
    local month = math.floor(n / 29) + 1
    if n >= _DAYS_BEFORE_MONTH[leap][month + 1] then month = month + 1 end
    local day = n - _DAYS_BEFORE_MONTH[leap][month] + 1
    return year, month, day
end

local function validate_date(y, m, d)
    if y < 1 or y > 9999 then Runtime.raise("ValueError", "year must be in 1..9999") end
    if m < 1 or m > 12 then Runtime.raise("ValueError", "month must be in 1..12") end
    local leap = is_leap(y)
    local dim = _DAYS_BEFORE_MONTH[leap][m+1] - _DAYS_BEFORE_MONTH[leap][m]
    if d < 1 or d > dim then Runtime.raise("ValueError", "day is out of range for month") end
end

local function validate_time(hr, min, sec, us)
    if hr < 0 or hr > 23 then Runtime.raise("ValueError", "hour must be in 0..23") end
    if min < 0 or min > 59 then Runtime.raise("ValueError", "minute must be in 0..59") end
    if sec < 0 or sec > 59 then Runtime.raise("ValueError", "second must be in 0..59") end
    if us < 0 or us > 999999 then Runtime.raise("ValueError", "microsecond must be in 0..999999") end
end

local function strftime_polyfill(fmt, dt)
    return (fmt:gsub("%%([a-zA-Z%%])", function(m)
        if m == "Y" then return string.format("%04d", dt._year or 0)
        elseif m == "m" then return string.format("%02d", dt._month or 0)
        elseif m == "d" then return string.format("%02d", dt._day or 0)
        elseif m == "H" then return string.format("%02d", dt._hour or 0)
        elseif m == "M" then return string.format("%02d", dt._minute or 0)
        elseif m == "S" then return string.format("%02d", dt._second or 0)
        elseif m == "f" then return string.format("%06d", dt._microsecond or 0)
        elseif m == "w" then return tostring((ymd2ord(dt._year, dt._month, dt._day) % 7))
        elseif m == "%" then return "%"
        end return "%" .. m
    end))
end

local function make_prop(key)
    return { __type = "property", fget = { __type = "builtin_method", __call = function(args) return args[1].__dict[key] end } }
end

-- 【新增】:精準還原 CPython 的 NotImplemented 異質比較信號
local function make_cmp(cmp_fn, op)
    return { __type = "builtin_method", __call = function(args)
        local c = cmp_fn(args[1], args[2])
        if c == nil then return require("Module:沙盒/a2569875/PythonScript/Runtime").builtins.NotImplemented end
        if op == "==" then return c == 0
        elseif op == "!=" then return c ~= 0
        elseif op == "<" then return c < 0
        elseif op == "<=" then return c <= 0
        elseif op == ">" then return c > 0
        elseif op == ">=" then return c >= 0 end
    end}
end

-- ==========================================
-- 1. timedelta 類別
-- ==========================================
local function normalize_td(d, s, us)
    local total_us = d * 86400000000 + s * 1000000 + us
    local rem = total_us % 86400000000
    local nd = math.floor(total_us / 86400000000)
    local ns = math.floor(rem / 1000000)
    local nus = rem % 1000000
    return nd, ns, nus
end

local function cmp_td(a, b)
    if type(b) ~= "table" or not b.__class or b.__class.__name ~= "timedelta" then return nil end
    local t1 = a.__dict._days * 86400000000 + a.__dict._seconds * 1000000 + a.__dict._microseconds
    local t2 = b.__dict._days * 86400000000 + b.__dict._seconds * 1000000 + b.__dict._microseconds
    if t1 < t2 then return -1 elseif t1 > t2 then return 1 else return 0 end
end

local timedelta_dict
timedelta_dict = {
    __init__ = { __type = "builtin_method", __call = function(args, kwargs)
        local self = args[1]; local kw = kwargs or {}
        local d = (args[2] or kw.days) or 0; local s = (args[3] or kw.seconds) or 0; local us = (args[4] or kw.microseconds) or 0
        local ms = (args[5] or kw.milliseconds) or 0; local min = (args[6] or kw.minutes) or 0
        local hr = (args[7] or kw.hours) or 0; local w = (args[8] or kw.weeks) or 0
        us = us + ms * 1000; s = s + min * 60 + hr * 3600; d = d + w * 7
        local nd, ns, nus = normalize_td(d, s, us)
        self.__dict._days, self.__dict._seconds, self.__dict._microseconds = nd, ns, nus; return nil
    end},
    days = make_prop("_days"), seconds = make_prop("_seconds"), microseconds = make_prop("_microseconds"),
    total_seconds = { __type = "builtin_method", __call = function(args) return args[1].__dict._days * 86400 + args[1].__dict._seconds + args[1].__dict._microseconds / 1000000.0 end},
    
    __add__ = { __type = "builtin_method", __call = function(args)
        local self, other = args[1], args[2]
        if other.__class and other.__class.__name == "timedelta" then
            local inst = PyClass.make_instance(self.__class)
            inst.__dict._days, inst.__dict._seconds, inst.__dict._microseconds = normalize_td(self.__dict._days + other.__dict._days, self.__dict._seconds + other.__dict._seconds, self.__dict._microseconds + other.__dict._microseconds)
            return inst
        end; return nil
    end},
    __sub__ = { __type = "builtin_method", __call = function(args)
        local self, other = args[1], args[2]
        if other.__class and other.__class.__name == "timedelta" then
            local inst = PyClass.make_instance(self.__class)
            inst.__dict._days, inst.__dict._seconds, inst.__dict._microseconds = normalize_td(self.__dict._days - other.__dict._days, self.__dict._seconds - other.__dict._seconds, self.__dict._microseconds - other.__dict._microseconds)
            return inst
        end; return nil
    end},
    __mul__ = { __type = "builtin_method", __call = function(args)
        local self, mult = args[1], args[2]
        if type(mult) ~= "number" then return nil end
        local inst = PyClass.make_instance(self.__class)
        inst.__dict._days, inst.__dict._seconds, inst.__dict._microseconds = normalize_td(self.__dict._days * mult, self.__dict._seconds * mult, self.__dict._microseconds * mult)
        return inst
    end},
    __truediv__ = { __type = "builtin_method", __call = function(args)
        local self, mult = args[1], args[2]
        if type(mult) == "number" then
            local inst = PyClass.make_instance(self.__class)
            inst.__dict._days, inst.__dict._seconds, inst.__dict._microseconds = normalize_td(self.__dict._days / mult, self.__dict._seconds / mult, self.__dict._microseconds / mult)
            return inst
        elseif type(mult) == "table" and mult.__class and mult.__class.__name == "timedelta" then
            local t1 = self.__dict._days * 86400000000 + self.__dict._seconds * 1000000 + self.__dict._microseconds
            local t2 = mult.__dict._days * 86400000000 + mult.__dict._seconds * 1000000 + mult.__dict._microseconds
            return t1 / t2
        end; return nil
    end},
    __floordiv__ = { __type = "builtin_method", __call = function(args)
        local res = timedelta_dict.__truediv__.__call(args); if type(res) == "number" then return math.floor(res) end
        if res and res.__class then res.__dict._days, res.__dict._seconds, res.__dict._microseconds = normalize_td(math.floor(res.__dict._days), math.floor(res.__dict._seconds), math.floor(res.__dict._microseconds)) end; return res
    end},
    __mod__ = { __type = "builtin_method", __call = function(args)
        local self, other = args[1], args[2]; if not other or other.__class.__name ~= "timedelta" then return nil end
        local t1 = self.__dict._days * 86400000000 + self.__dict._seconds * 1000000 + self.__dict._microseconds
        local t2 = other.__dict._days * 86400000000 + other.__dict._seconds * 1000000 + other.__dict._microseconds
        local rem = t1 % t2; local inst = PyClass.make_instance(self.__class)
        inst.__dict._days, inst.__dict._seconds, inst.__dict._microseconds = normalize_td(0, 0, rem); return inst
    end},
    __divmod__ = { __type = "builtin_method", __call = function(args)
        local q = timedelta_dict.__floordiv__.__call(args); local r = timedelta_dict.__mod__.__call(args)
        if q == nil or r == nil then return nil end; return { __type = "tuple", elements = {q, r} }
    end},
    __pos__ = { __type = "builtin_method", __call = function(args) return args[1] end},
    __neg__ = { __type = "builtin_method", __call = function(args)
        local self = args[1]; local inst = PyClass.make_instance(self.__class)
        inst.__dict._days, inst.__dict._seconds, inst.__dict._microseconds = normalize_td(-self.__dict._days, -self.__dict._seconds, -self.__dict._microseconds); return inst
    end},
    __abs__ = { __type = "builtin_method", __call = function(args)
        local self = args[1]; if self.__dict._days < 0 then return timedelta_dict.__neg__.__call(args) end; return self
    end},
    __bool__ = { __type = "builtin_method", __call = function(args) local self = args[1]; return self.__dict._days ~= 0 or self.__dict._seconds ~= 0 or self.__dict._microseconds ~= 0 end},

    __eq__ = make_cmp(cmp_td, "=="),
    __ne__ = make_cmp(cmp_td, "!="),
    __lt__ = make_cmp(cmp_td, "<"),
    __le__ = make_cmp(cmp_td, "<="),
    __gt__ = make_cmp(cmp_td, ">"),
    __ge__ = make_cmp(cmp_td, ">="),

    __str__ = { __type = "builtin_method", __call = function(args)
        local self = args[1]; local mm, ss = math.floor(self.__dict._seconds / 60), self.__dict._seconds % 60
        local hh, mm = math.floor(mm / 60), mm % 60; local s = string.format("%d:%02d:%02d", hh, mm, ss)
        if self.__dict._days ~= 0 then s = self.__dict._days .. " day" .. (math.abs(self.__dict._days) ~= 1 and "s, " or ", ") .. s end
        if self.__dict._microseconds ~= 0 then s = s .. string.format(".%06d", self.__dict._microseconds) end; return s
    end},
    __repr__ = { __type = "builtin_method", __call = function(args) local self = args[1]; return "datetime.timedelta(days=" .. self.__dict._days .. ", seconds=" .. self.__dict._seconds .. ", microseconds=" .. self.__dict._microseconds .. ")" end}
}
timedelta_dict.__radd__ = timedelta_dict.__add__
timedelta_dict.__rsub__ = { __type = "builtin_method", __call = function(args)
    local self, other = args[1], args[2]
    if other.__class and other.__class.__name == "timedelta" then
        local inst = PyClass.make_instance(self.__class)
        inst.__dict._days, inst.__dict._seconds, inst.__dict._microseconds = normalize_td(other.__dict._days - self.__dict._days, other.__dict._seconds - self.__dict._seconds, other.__dict._microseconds - self.__dict._microseconds)
        return inst
    end; return nil
end}
timedelta_dict.__rmul__ = timedelta_dict.__mul__
timedelta_dict.__rfloordiv__ = { __type = "builtin_method", __call = function(args) return nil end}
timedelta_dict.__rmod__ = { __type = "builtin_method", __call = function(args) return nil end}
timedelta_dict.__rdivmod__ = { __type = "builtin_method", __call = function(args) return nil end}
local TimedeltaClass = PyClass.make_class("timedelta", {}, timedelta_dict)

-- ==========================================
-- 2. tzinfo 與 timezone
-- ==========================================
local tzinfo_dict = {
    __init__ = { __type = "builtin_method", __call = function() return nil end },
    tzname = { __type = "builtin_method", __call = function() return nil end },
    utcoffset = { __type = "builtin_method", __call = function() return nil end },
    dst = { __type = "builtin_method", __call = function() return nil end },
    fromutc = { __type = "builtin_method", __call = function() return nil end },
    __eq__ = { __type = "builtin_method", __call = function(args) return args[1] == args[2] end },
    __ne__ = { __type = "builtin_method", __call = function(args) return args[1] ~= args[2] end }
}
local TzinfoClass = PyClass.make_class("tzinfo", {}, tzinfo_dict)

local timezone_dict = {
    __init__ = { __type = "builtin_method", __call = function(args, kwargs)
        local self = args[1]; self.__dict._offset = args[2]; self.__dict._name = args[3] or (kwargs and kwargs.name) or nil; return nil
    end},
    utcoffset = { __type = "builtin_method", __call = function(args) return args[1].__dict._offset end },
    tzname = { __type = "builtin_method", __call = function(args) return args[1].__dict._name end },
    dst = { __type = "builtin_method", __call = function(args) return nil end },
    fromutc = { __type = "builtin_method", __call = function(args) return args[2] end },
    __str__ = { __type = "builtin_method", __call = function(args) return args[1].__dict._name or "timezone" end }
}
local TimezoneClass = PyClass.make_class("timezone", {TzinfoClass}, timezone_dict)

-- ==========================================
-- 3. time 類別
-- ==========================================
local function cmp_time(a, b)
    if type(b) ~= "table" or not b.__class or b.__class.__name ~= "time" then return nil end
    if a.__dict._hour ~= b.__dict._hour then return a.__dict._hour < b.__dict._hour and -1 or 1 end
    if a.__dict._minute ~= b.__dict._minute then return a.__dict._minute < b.__dict._minute and -1 or 1 end
    if a.__dict._second ~= b.__dict._second then return a.__dict._second < b.__dict._second and -1 or 1 end
    if a.__dict._microsecond ~= b.__dict._microsecond then return a.__dict._microsecond < b.__dict._microsecond and -1 or 1 end
    return 0
end

local time_dict = {
    __init__ = { __type = "builtin_method", __call = function(args, kwargs)
        local self = args[1]; local kw = kwargs or {}
        local hr = args[2] or kw.hour or 0; local min = args[3] or kw.minute or 0
        local sec = args[4] or kw.second or 0; local us = args[5] or kw.microsecond or 0; local tz = args[6] or kw.tzinfo or nil
        validate_time(hr, min, sec, us)
        self.__dict._hour, self.__dict._minute, self.__dict._second, self.__dict._microsecond, self.__dict._tzinfo, self.__dict._fold = hr, min, sec, us, tz, kw.fold or 0; return nil
    end},
    hour = make_prop("_hour"), minute = make_prop("_minute"), second = make_prop("_second"), microsecond = make_prop("_microsecond"), tzinfo = make_prop("_tzinfo"), fold = make_prop("_fold"),
    
    replace = { __type = "builtin_method", __call = function(args, kwargs)
        local self = args[1]; local kw = kwargs or {}
        local hr, min, sec, us, tz, fold = kw.hour or self.__dict._hour, kw.minute or self.__dict._minute, kw.second or self.__dict._second, kw.microsecond or self.__dict._microsecond, kw.tzinfo or self.__dict._tzinfo, kw.fold or self.__dict._fold
        local inst = PyClass.make_instance(self.__class)
        inst.__dict._hour, inst.__dict._minute, inst.__dict._second, inst.__dict._microsecond, inst.__dict._tzinfo, inst.__dict._fold = hr, min, sec, us, tz, fold; return inst
    end},
    fromisoformat = { __type = "classmethod", __func = { __type = "builtin_method", __call = function(args)
        local cls, s = args[1], args[2]; local h, m, sec, us = s:match("^(%d%d):(%d%d):(%d%d)%.?(%d*)$")
        if not h then Runtime.raise("ValueError", "Invalid isoformat string") end
        us = us == "" and 0 or tonumber(string.sub(us .. "000000", 1, 6))
        local inst = PyClass.make_instance(cls)
        inst.__dict._hour, inst.__dict._minute, inst.__dict._second, inst.__dict._microsecond, inst.__dict._tzinfo, inst.__dict._fold = tonumber(h), tonumber(m), tonumber(sec), us, nil, 0; return inst
    end}},
    tzname = { __type = "builtin_method", __call = function(args) local t = args[1].__dict._tzinfo; return t and t.__dict.tzname.__call({t}) or nil end},
    utcoffset = { __type = "builtin_method", __call = function(args) local t = args[1].__dict._tzinfo; return t and t.__dict.utcoffset.__call({t}) or nil end},
    dst = { __type = "builtin_method", __call = function(args) local t = args[1].__dict._tzinfo; return t and t.__dict.dst.__call({t}) or nil end},
    isoformat = { __type = "builtin_method", __call = function(args)
        local self = args[1]; local s = string.format("%02d:%02d:%02d", self.__dict._hour, self.__dict._minute, self.__dict._second)
        if self.__dict._microsecond > 0 then s = s .. string.format(".%06d", self.__dict._microsecond) end; return s
    end},
    strftime = { __type = "builtin_method", __call = function(args) return strftime_polyfill(args[2], args[1].__dict) end },
    __format__ = { __type = "builtin_method", __call = function(args)
        local self, fmt = args[1], args[2] or ""
        if type(fmt) ~= "string" then Runtime.raise("TypeError", "must be str") end
        if fmt == "" then return Runtime.builtins.str.__call({self}) end
        return strftime_polyfill(fmt, self.__dict)
    end },
    
    __eq__ = make_cmp(cmp_time, "=="),
    __ne__ = make_cmp(cmp_time, "!="),
    __lt__ = make_cmp(cmp_time, "<"),
    __le__ = make_cmp(cmp_time, "<="),
    __gt__ = make_cmp(cmp_time, ">"),
    __ge__ = make_cmp(cmp_time, ">="),
    __str__ = { __type = "builtin_method", __call = function(args) return args[1].__dict.isoformat.__call(args) end }
}
local TimeClass = PyClass.make_class("time", {}, time_dict)

-- ==========================================
-- 4. date 類別
-- ==========================================
local function cmp_date(a, b)
    if type(b) ~= "table" or not b.__class or (b.__class.__name ~= "date" and b.__class.__name ~= "datetime") then return nil end
    local o1 = ymd2ord(a.__dict._year, a.__dict._month, a.__dict._day)
    local o2 = ymd2ord(b.__dict._year, b.__dict._month, b.__dict._day)
    if o1 < o2 then return -1 elseif o1 > o2 then return 1 else return 0 end
end

local date_dict = {
    __init__ = { __type = "builtin_method", __call = function(args, kwargs)
        local self = args[1]; local kw = kwargs or {}
        local y, m, d = args[2] or kw.year, args[3] or kw.month, args[4] or kw.day
        validate_date(y, m, d)
        self.__dict._year, self.__dict._month, self.__dict._day = y, m, d; return nil
    end},
    year = make_prop("_year"), month = make_prop("_month"), day = make_prop("_day"),
    
    replace = { __type = "builtin_method", __call = function(args, kwargs)
        local self = args[1]; local kw = kwargs or {}
        local inst = PyClass.make_instance(self.__class)
        inst.__dict._year, inst.__dict._month, inst.__dict._day = kw.year or self.__dict._year, kw.month or self.__dict._month, kw.day or self.__dict._day; return inst
    end},
    today = { __type = "classmethod", __func = { __type = "builtin_method", __call = function(args)
        local cls = args[1]; local t = os.date("*t")
        local inst = PyClass.make_instance(cls)
        inst.__dict._year, inst.__dict._month, inst.__dict._day = t.year, t.month, t.day; return inst
    end}},
    fromtimestamp = { __type = "classmethod", __func = { __type = "builtin_method", __call = function(args)
        local cls = args[1]; local t = os.date("*t", args[2])
        local inst = PyClass.make_instance(cls)
        inst.__dict._year, inst.__dict._month, inst.__dict._day = t.year, t.month, t.day; return inst
    end}},
    fromordinal = { __type = "classmethod", __func = { __type = "builtin_method", __call = function(args)
        local cls = args[1]; local y, m, d = ord2ymd(args[2])
        local inst = PyClass.make_instance(cls)
        inst.__dict._year, inst.__dict._month, inst.__dict._day = y, m, d; return inst
    end}},
    fromisoformat = { __type = "classmethod", __func = { __type = "builtin_method", __call = function(args)
        local cls, s = args[1], args[2]; local y, m, d = s:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)$")
        if not y then Runtime.raise("ValueError", "Invalid isoformat string") end
        local inst = PyClass.make_instance(cls)
        inst.__dict._year, inst.__dict._month, inst.__dict._day = tonumber(y), tonumber(m), tonumber(d); return inst
    end}},
    toordinal = { __type = "builtin_method", __call = function(args) return ymd2ord(args[1].__dict._year, args[1].__dict._month, args[1].__dict._day) end},
    weekday = { __type = "builtin_method", __call = function(args) return (ymd2ord(args[1].__dict._year, args[1].__dict._month, args[1].__dict._day) - 1) % 7 end},
    isoweekday = { __type = "builtin_method", __call = function(args) return ((ymd2ord(args[1].__dict._year, args[1].__dict._month, args[1].__dict._day) - 1) % 7) + 1 end},
    isocalendar = { __type = "builtin_method", __call = function(args)
        local self = args[1]
        local d = ymd2ord(self.__dict._year, self.__dict._month, self.__dict._day)
        local d4 = (d + 3) % 7
        local d0 = d - d4
        local y, _, _ = ord2ymd(d0)
        local d0_year_start = ymd2ord(y, 1, 1)
        local week = math.floor((d0 - d0_year_start) / 7) + 1
        return { __type = "tuple", elements = {y, week, ((d - 1) % 7) + 1} }
    end},
    timetuple = { __type = "builtin_method", __call = function(args)
        local self = args[1]; local d = self.__dict
        local yday = ymd2ord(d._year, d._month, d._day) - ymd2ord(d._year, 1, 1) + 1
        local wday = (ymd2ord(d._year, d._month, d._day) - 1) % 7
        return { __type = "tuple", elements = {d._year, d._month, d._day, d._hour or 0, d._minute or 0, d._second or 0, wday, yday, -1} }
    end},
    isoformat = { __type = "builtin_method", __call = function(args) return string.format("%04d-%02d-%02d", args[1].__dict._year, args[1].__dict._month, args[1].__dict._day) end},
    strftime = { __type = "builtin_method", __call = function(args) return strftime_polyfill(args[2], args[1].__dict) end },
    __format__ = { __type = "builtin_method", __call = function(args)
        local self, fmt = args[1], args[2] or ""
        if type(fmt) ~= "string" then Runtime.raise("TypeError", "must be str") end
        if fmt == "" then return Runtime.builtins.str.__call({self}) end
        return strftime_polyfill(fmt, self.__dict)
    end },
    ctime = { __type = "builtin_method", __call = function(args)
        local d = args[1].__dict
        local days = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; local months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
        local wday = days[((ymd2ord(d._year, d._month, d._day) - 1) % 7) + 1]
        local mname = months[d._month]
        return string.format("%s %s %2d %02d:%02d:%02d %04d", wday, mname, d._day, d._hour or 0, d._minute or 0, d._second or 0, d._year)
    end},
    
    __eq__ = make_cmp(cmp_date, "=="),
    __ne__ = make_cmp(cmp_date, "!="),
    __lt__ = make_cmp(cmp_date, "<"),
    __le__ = make_cmp(cmp_date, "<="),
    __gt__ = make_cmp(cmp_date, ">"),
    __ge__ = make_cmp(cmp_date, ">="),
    
    __str__ = { __type = "builtin_method", __call = function(args) return args[1].__dict.isoformat.__call(args) end },
    __add__ = { __type = "builtin_method", __call = function(args)
        local self, other = args[1], args[2]
        if other.__class and other.__class.__name == "timedelta" then
            local ord = ymd2ord(self.__dict._year, self.__dict._month, self.__dict._day) + other.__dict._days
            local ny, nm, nd = ord2ymd(ord); local inst = PyClass.make_instance(self.__class)
            inst.__dict._year, inst.__dict._month, inst.__dict._day = ny, nm, nd; return inst
        end; return nil
    end},
    __sub__ = { __type = "builtin_method", __call = function(args)
        local self, other = args[1], args[2]
        if other.__class and other.__class.__name == "timedelta" then
            local ord = ymd2ord(self.__dict._year, self.__dict._month, self.__dict._day) - other.__dict._days
            local ny, nm, nd = ord2ymd(ord); local inst = PyClass.make_instance(self.__class)
            inst.__dict._year, inst.__dict._month, inst.__dict._day = ny, nm, nd; return inst
        elseif other.__class and (other.__class.__name == "date" or other.__class.__name == "datetime") then
            local td = PyClass.make_instance(TimedeltaClass)
            td.__dict._days = ymd2ord(self.__dict._year, self.__dict._month, self.__dict._day) - ymd2ord(other.__dict._year, other.__dict._month, other.__dict._day)
            td.__dict._seconds, td.__dict._microseconds = 0, 0; return td
        end; return nil
    end}
}
date_dict.__radd__ = date_dict.__add__
date_dict.__rsub__ = { __type = "builtin_method", __call = function(args) return nil end }
local DateClass = PyClass.make_class("date", {}, date_dict)

-- ==========================================
-- 5. datetime 類別
-- ==========================================
local function datetime_add_td(dt, td)
    local total_us = dt._hour * 3600000000 + dt._minute * 60000000 + dt._second * 1000000 + dt._microsecond
    total_us = total_us + td._seconds * 1000000 + td._microseconds
    local extra_days = math.floor(total_us / 86400000000); local rem_us = total_us % 86400000000
    local ord = ymd2ord(dt._year, dt._month, dt._day) + td._days + extra_days
    local ny, nm, nd = ord2ymd(ord)
    local nhr = math.floor(rem_us / 3600000000); rem_us = rem_us % 3600000000
    local nmin = math.floor(rem_us / 60000000); rem_us = rem_us % 60000000
    local nsec = math.floor(rem_us / 1000000); local nus = rem_us % 1000000
    return ny, nm, nd, nhr, nmin, nsec, nus
end

local function cmp_dt(a, b)
    if type(b) ~= "table" or not b.__class or b.__class.__name ~= "datetime" then return nil end
    local c = cmp_date(a, b); if c ~= 0 then return c end
    if a.__dict._hour ~= b.__dict._hour then return a.__dict._hour < b.__dict._hour and -1 or 1 end
    if a.__dict._minute ~= b.__dict._minute then return a.__dict._minute < b.__dict._minute and -1 or 1 end
    if a.__dict._second ~= b.__dict._second then return a.__dict._second < b.__dict._second and -1 or 1 end
    if a.__dict._microsecond ~= b.__dict._microsecond then return a.__dict._microsecond < b.__dict._microsecond and -1 or 1 end
    return 0
end

local datetime_dict = {
    __init__ = { __type = "builtin_method", __call = function(args, kwargs)
        local self = args[1]; local kw = kwargs or {}
        local y, m, d = args[2] or kw.year, args[3] or kw.month, args[4] or kw.day
        local hr, min, sec, us = args[5] or kw.hour or 0, args[6] or kw.minute or 0, args[7] or kw.second or 0, args[8] or kw.microsecond or 0
        local tz = args[9] or kw.tzinfo or nil
        validate_date(y, m, d); validate_time(hr, min, sec, us)
        self.__dict._year, self.__dict._month, self.__dict._day = y, m, d
        self.__dict._hour, self.__dict._minute, self.__dict._second, self.__dict._microsecond, self.__dict._tzinfo, self.__dict._fold = hr, min, sec, us, tz, kw.fold or 0; return nil
    end},
    year = make_prop("_year"), month = make_prop("_month"), day = make_prop("_day"), hour = make_prop("_hour"), minute = make_prop("_minute"), second = make_prop("_second"), microsecond = make_prop("_microsecond"), tzinfo = make_prop("_tzinfo"), fold = make_prop("_fold"),
    
    replace = { __type = "builtin_method", __call = function(args, kwargs)
        local self = args[1]; local kw = kwargs or {}
        local inst = PyClass.make_instance(self.__class)
        inst.__dict._year, inst.__dict._month, inst.__dict._day = kw.year or self.__dict._year, kw.month or self.__dict._month, kw.day or self.__dict._day
        inst.__dict._hour, inst.__dict._minute, inst.__dict._second, inst.__dict._microsecond, inst.__dict._tzinfo, inst.__dict._fold = kw.hour or self.__dict._hour, kw.minute or self.__dict._minute, kw.second or self.__dict._second, kw.microsecond or self.__dict._microsecond, kw.tzinfo or self.__dict._tzinfo, kw.fold or self.__dict._fold
        return inst
    end},
    now = { __type = "classmethod", __func = { __type = "builtin_method", __call = function(args, kwargs)
        local cls = args[1]; local tz = args[2] or (kwargs and kwargs.tz)
        local t = os.date(tz and "!*t" or "*t")
        local inst = PyClass.make_instance(cls)
        inst.__dict._year, inst.__dict._month, inst.__dict._day, inst.__dict._hour, inst.__dict._minute, inst.__dict._second, inst.__dict._microsecond, inst.__dict._tzinfo, inst.__dict._fold = t.year, t.month, t.day, t.hour, t.min, t.sec, 0, tz, 0; return inst
    end}},
    utcnow = { __type = "classmethod", __func = { __type = "builtin_method", __call = function(args)
        local cls = args[1]; local t = os.date("!*t")
        local inst = PyClass.make_instance(cls)
        inst.__dict._year, inst.__dict._month, inst.__dict._day, inst.__dict._hour, inst.__dict._minute, inst.__dict._second, inst.__dict._microsecond, inst.__dict._tzinfo, inst.__dict._fold = t.year, t.month, t.day, t.hour, t.min, t.sec, 0, nil, 0; return inst
    end}},
    combine = { __type = "classmethod", __func = { __type = "builtin_method", __call = function(args)
        local cls, d_obj, t_obj, tzinfo = args[1], args[2], args[3], args[4]
        if not tzinfo then tzinfo = t_obj.__dict._tzinfo end
        local inst = PyClass.make_instance(cls)
        inst.__dict._year, inst.__dict._month, inst.__dict._day = d_obj.__dict._year, d_obj.__dict._month, d_obj.__dict._day
        inst.__dict._hour, inst.__dict._minute, inst.__dict._second, inst.__dict._microsecond, inst.__dict._tzinfo, inst.__dict._fold = t_obj.__dict._hour, t_obj.__dict._minute, t_obj.__dict._second, t_obj.__dict._microsecond, tzinfo, t_obj.__dict._fold; return inst
    end}},
    fromisoformat = { __type = "classmethod", __func = { __type = "builtin_method", __call = function(args)
        local cls, s = args[1], args[2]; local y, mo, d, h, m, sec, us = s:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)[T ](%d%d):(%d%d):(%d%d)%.?(%d*)$")
        if not y then Runtime.raise("ValueError", "Invalid isoformat string") end
        us = us == "" and 0 or tonumber(string.sub(us .. "000000", 1, 6))
        local inst = PyClass.make_instance(cls)
        inst.__dict._year, inst.__dict._month, inst.__dict._day = tonumber(y), tonumber(mo), tonumber(d)
        inst.__dict._hour, inst.__dict._minute, inst.__dict._second, inst.__dict._microsecond, inst.__dict._tzinfo, inst.__dict._fold = tonumber(h), tonumber(m), tonumber(sec), us, nil, 0; return inst
    end}},
    date = { __type = "builtin_method", __call = function(args)
        local self = args[1]; local inst = PyClass.make_instance(DateClass)
        inst.__dict._year, inst.__dict._month, inst.__dict._day = self.__dict._year, self.__dict._month, self.__dict._day; return inst
    end},
    time = { __type = "builtin_method", __call = function(args)
        local self = args[1]; local inst = PyClass.make_instance(TimeClass)
        inst.__dict._hour, inst.__dict._minute, inst.__dict._second, inst.__dict._microsecond, inst.__dict._tzinfo, inst.__dict._fold = self.__dict._hour, self.__dict._minute, self.__dict._second, self.__dict._microsecond, nil, self.__dict._fold; return inst
    end},
    timetz = { __type = "builtin_method", __call = function(args)
        local self = args[1]; local inst = PyClass.make_instance(TimeClass)
        inst.__dict._hour, inst.__dict._minute, inst.__dict._second, inst.__dict._microsecond, inst.__dict._tzinfo, inst.__dict._fold = self.__dict._hour, self.__dict._minute, self.__dict._second, self.__dict._microsecond, self.__dict._tzinfo, self.__dict._fold; return inst
    end},
    tzname = { __type = "builtin_method", __call = function(args) local t = args[1].__dict._tzinfo; return t and t.__dict.tzname.__call({t}) or nil end},
    utcoffset = { __type = "builtin_method", __call = function(args) local t = args[1].__dict._tzinfo; return t and t.__dict.utcoffset.__call({t}) or nil end},
    dst = { __type = "builtin_method", __call = function(args) local t = args[1].__dict._tzinfo; return t and t.__dict.dst.__call({t}) or nil end},
    isoformat = { __type = "builtin_method", __call = function(args)
        local self = args[1]; local s = string.format("%04d-%02d-%02dT%02d:%02d:%02d", self.__dict._year, self.__dict._month, self.__dict._day, self.__dict._hour, self.__dict._minute, self.__dict._second)
        if self.__dict._microsecond > 0 then s = s .. string.format(".%06d", self.__dict._microsecond) end; return s
    end},
    
    __eq__ = make_cmp(cmp_dt, "=="),
    __ne__ = make_cmp(cmp_dt, "!="),
    __lt__ = make_cmp(cmp_dt, "<"),
    __le__ = make_cmp(cmp_dt, "<="),
    __gt__ = make_cmp(cmp_dt, ">"),
    __ge__ = make_cmp(cmp_dt, ">="),
    
    __str__ = { __type = "builtin_method", __call = function(args)
        local self = args[1]; local s = string.format("%04d-%02d-%02d %02d:%02d:%02d", self.__dict._year, self.__dict._month, self.__dict._day, self.__dict._hour, self.__dict._minute, self.__dict._second)
        if self.__dict._microsecond > 0 then s = s .. string.format(".%06d", self.__dict._microsecond) end; return s
    end},
    __add__ = { __type = "builtin_method", __call = function(args)
        local self, other = args[1], args[2]
        if other.__class and other.__class.__name == "timedelta" then
            local ny, nm, nd, nhr, nmin, nsec, nus = datetime_add_td(self.__dict, other.__dict)
            local inst = PyClass.make_instance(self.__class)
            inst.__dict._year, inst.__dict._month, inst.__dict._day = ny, nm, nd
            inst.__dict._hour, inst.__dict._minute, inst.__dict._second, inst.__dict._microsecond, inst.__dict._tzinfo, inst.__dict._fold = nhr, nmin, nsec, nus, self.__dict._tzinfo, self.__dict._fold; return inst
        end; return nil
    end},
    __sub__ = { __type = "builtin_method", __call = function(args)
        local self, other = args[1], args[2]
        if other.__class and other.__class.__name == "timedelta" then
            local td_neg = {_days = -other.__dict._days, _seconds = -other.__dict._seconds, _microseconds = -other.__dict._microseconds}
            local ny, nm, nd, nhr, nmin, nsec, nus = datetime_add_td(self.__dict, td_neg)
            local inst = PyClass.make_instance(self.__class)
            inst.__dict._year, inst.__dict._month, inst.__dict._day = ny, nm, nd
            inst.__dict._hour, inst.__dict._minute, inst.__dict._second, inst.__dict._microsecond, inst.__dict._tzinfo, inst.__dict._fold = nhr, nmin, nsec, nus, self.__dict._tzinfo, self.__dict._fold; return inst
        elseif other.__class and other.__class.__name == "datetime" then
            local d1 = ymd2ord(self.__dict._year, self.__dict._month, self.__dict._day)
            local d2 = ymd2ord(other.__dict._year, other.__dict._month, other.__dict._day)
            local s1 = self.__dict._hour * 3600 + self.__dict._minute * 60 + self.__dict._second
            local s2 = other.__dict._hour * 3600 + other.__dict._minute * 60 + other.__dict._second
            local td = PyClass.make_instance(TimedeltaClass)
            td.__dict._days, td.__dict._seconds, td.__dict._microseconds = normalize_td(d1 - d2, s1 - s2, self.__dict._microsecond - other.__dict._microsecond)
            return td
        end; return nil
    end}
}
datetime_dict.__radd__ = datetime_dict.__add__
datetime_dict.__rsub__ = { __type = "builtin_method", __call = function(args) return nil end }
local DatetimeClass = PyClass.make_class("datetime", {DateClass}, datetime_dict)

-- ==========================================
-- 注入類別常數與綁定
-- ==========================================
local function inject_constants()
    -- UTC Timezone
    local utc_td = PyClass.make_instance(TimedeltaClass); utc_td.__dict._days, utc_td.__dict._seconds, utc_td.__dict._microseconds = 0, 0, 0
    local utc_inst = PyClass.make_instance(TimezoneClass); utc_inst.__dict._offset = utc_td; utc_inst.__dict._name = "UTC"
    PyClass.set_attr(TimezoneClass, "utc", utc_inst)
    
    -- Timedelta Constants
    local td_min = PyClass.make_instance(TimedeltaClass); td_min.__dict._days, td_min.__dict._seconds, td_min.__dict._microseconds = -999999999, 0, 0
    local td_max = PyClass.make_instance(TimedeltaClass); td_max.__dict._days, td_max.__dict._seconds, td_max.__dict._microseconds = 999999999, 86399, 999999
    local td_res = PyClass.make_instance(TimedeltaClass); td_res.__dict._days, td_res.__dict._seconds, td_res.__dict._microseconds = 0, 0, 1
    PyClass.set_attr(TimedeltaClass, "min", td_min); PyClass.set_attr(TimedeltaClass, "max", td_max); PyClass.set_attr(TimedeltaClass, "resolution", td_res)
    
    -- Date Constants
    local d_min = PyClass.make_instance(DateClass); d_min.__dict._year, d_min.__dict._month, d_min.__dict._day = 1, 1, 1
    local d_max = PyClass.make_instance(DateClass); d_max.__dict._year, d_max.__dict._month, d_max.__dict._day = 9999, 12, 31
    PyClass.set_attr(DateClass, "min", d_min); PyClass.set_attr(DateClass, "max", d_max); PyClass.set_attr(DateClass, "resolution", td_res)
    
    -- Time Constants
    local t_min = PyClass.make_instance(TimeClass); t_min.__dict._hour, t_min.__dict._minute, t_min.__dict._second, t_min.__dict._microsecond = 0, 0, 0, 0
    local t_max = PyClass.make_instance(TimeClass); t_max.__dict._hour, t_max.__dict._minute, t_max.__dict._second, t_max.__dict._microsecond = 23, 59, 59, 999999
    PyClass.set_attr(TimeClass, "min", t_min); PyClass.set_attr(TimeClass, "max", t_max); PyClass.set_attr(TimeClass, "resolution", td_res)
    
    -- Datetime Constants
    local dt_min = PyClass.make_instance(DatetimeClass); dt_min.__dict._year, dt_min.__dict._month, dt_min.__dict._day, dt_min.__dict._hour, dt_min.__dict._minute, dt_min.__dict._second, dt_min.__dict._microsecond = 1, 1, 1, 0, 0, 0, 0
    local dt_max = PyClass.make_instance(DatetimeClass); dt_max.__dict._year, dt_max.__dict._month, dt_max.__dict._day, dt_max.__dict._hour, dt_max.__dict._minute, dt_max.__dict._second, dt_max.__dict._microsecond = 9999, 12, 31, 23, 59, 59, 999999
    PyClass.set_attr(DatetimeClass, "min", dt_min); PyClass.set_attr(DatetimeClass, "max", dt_max); PyClass.set_attr(DatetimeClass, "resolution", td_res)
end
inject_constants()

function DatetimeMod.get_module()
    local dict = {
        MAXYEAR = 9999, MINYEAR = 1,
        timedelta = TimedeltaClass, tzinfo = TzinfoClass, timezone = TimezoneClass, time = TimeClass, date = DateClass, datetime = DatetimeClass
    }
    local inst = PyClass.make_instance(PyClass.make_class("module", {}, dict))
    inst.__dict["__name__"] = "datetime"
    return inst
end

-- ==========================================
-- time 模組 (系統時間、高精度計時器與時間結構轉換)
-- ==========================================
function DatetimeMod.get_time_module()
    local B = Runtime.builtins
    local Evaluator = require("Module:沙盒/a2569875/PythonScript/Evaluator")
    local EvalUtils = require("Module:沙盒/a2569875/PythonScript/Evaluator/Utils")
    local u_num = require("Module:沙盒/a2569875/PythonScript/Runtime/Utils").unwrap_num

    local base_epoch = os.time() or 0
    local base_clock = os.clock() or 0
    local virtual_sleep = 0

    local function is_none(v)
        return v == nil or (type(v) == "table" and v.__class and v.__class.__name == "NoneType")
    end

    local function get_wall_time()
        return base_epoch + ((os.clock() or 0) - base_clock) + virtual_sleep
    end

    local function get_perf_time()
        return (os.clock() or 0) + virtual_sleep
    end

    local function get_cpu_time()
        return os.clock() or 0
    end

    -- 1. struct_time 類別
    local st_field_names = {
        "tm_year", "tm_mon", "tm_mday", "tm_hour", "tm_min",
        "tm_sec", "tm_wday", "tm_yday", "tm_isdst"
    }

    local struct_time_cls
    local function make_struct_time(elts, zone, gmtoff)
        local inst = PyClass.make_instance(struct_time_cls)
        local seq_elts = {}
        for i = 1, 9 do
            local v = tonumber(u_num(elts[i])) or 0
            seq_elts[i] = v
            inst.__dict[st_field_names[i]] = v
        end
        inst.__dict.tm_zone = zone or elts[10] or "UTC"
        inst.__dict.tm_gmtoff = gmtoff or tonumber(elts[11] and u_num(elts[11])) or 0
        inst.__dict.n_fields = 11
        inst.__dict.n_sequence_fields = 9
        inst.__dict.n_unnamed_fields = 0
        inst.__builtin_value = { __type = "tuple", elements = seq_elts }
        return inst
    end

    local struct_time_dict = {
        n_fields = 11,
        n_sequence_fields = 9,
        n_unnamed_fields = 0,
        __new__ = { __type = "builtin_method", __call = function(a)
            local seq = a[2]
            if is_none(seq) then Runtime.raise("TypeError", "time.struct_time() missing required argument 'sequence'") end
            local lst = B.list.__call({seq}).elements
            if #lst < 9 or #lst > 11 then
                Runtime.raise("TypeError", "time.struct_time() takes an at least 9-sequence (" .. #lst .. "-sequence given)")
            end
            return make_struct_time(lst)
        end },
        __repr__ = { __type = "builtin_method", __call = function(a)
            local d = a[1].__dict
            local parts = {}
            for i = 1, 9 do
                local k = st_field_names[i]
                parts[i] = k .. "=" .. tostring(d[k])
            end
            return "time.struct_time((" .. table.concat(parts, ", ") .. "))"
        end },
        __len__ = { __type = "builtin_method", __call = function() return 9 end },
        __iter__ = { __type = "builtin_method", __call = function(a)
            return B.iter({ a[1].__builtin_value })
        end },
        __eq__ = { __type = "builtin_method", __call = function(a)
            local s, o = a[1], a[2]
            local o_tup = (type(o) == "table" and o.__builtin_value) or o
            return EvalUtils.py_eq(s.__builtin_value, o_tup)
        end }
    }
    struct_time_dict.__str__ = struct_time_dict.__repr__
    struct_time_cls = PyClass.make_class("struct_time", { B.tuple }, struct_time_dict)
    struct_time_cls.__dict["__module__"] = "time"

    -- 將 Lua os.date("*t") 轉為 Python struct_time (Python: 週一=0 .. 週日=6)
    local function lua_date_to_struct(t, is_utc)
        local py_wday = ((t.wday or 1) - 2) % 7
        local isdst = (t.isdst == true and 1) or (t.isdst == false and 0) or -1
        return make_struct_time(
            { t.year, t.month, t.day, t.hour, t.min, t.sec, py_wday, t.yday or 1, isdst },
            is_utc and "UTC" or "UTC",
            0
        )
    end

    -- 將 Python struct_time 或 9-tuple 解包為 9 個整數陣列與 os.time 表
    local function extract_tuple9(t_obj)
        if is_none(t_obj) then
            local now_ts = math.floor(get_wall_time())
            t_obj = lua_date_to_struct(os.date("*t", now_ts), false)
        end
        local elts
        if type(t_obj) == "table" and t_obj.__builtin_value then
            elts = t_obj.__builtin_value.elements
        elseif type(t_obj) == "table" and (t_obj.__type == "tuple" or t_obj.__type == "list") then
            elts = t_obj.elements
        else
            elts = B.list.__call({t_obj}).elements
        end
        if not elts or #elts ~= 9 then
            Runtime.raise("TypeError", "Tuple or struct_time argument required (got " .. tostring(elts and #elts or 0) .. " elements)")
        end
        local r = {}
        for i = 1, 9 do r[i] = math.floor(tonumber(u_num(elts[i])) or 0) end
        return r
    end

    local wday_abbr = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }
    local wday_full = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }
    local mon_abbr = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }
    local mon_full = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }

    local function format_asctime(r)
        local wd = wday_abbr[(r[7] % 7) + 1] or "Mon"
        local mn = mon_abbr[r[2]] or "Jan"
        return string.format("%s %s %2d %02d:%02d:%02d %04d", wd, mn, r[3], r[4], r[5], r[6], r[1])
    end

    -- 2. get_clock_info 回傳用之 SimpleNamespace 類別
    local clock_info_cls = PyClass.make_class("namespace", {}, {
        __repr__ = { __type = "builtin_method", __call = function(a)
            local d = a[1].__dict
            return string.format("namespace(adjustable=%s, implementation=%s, monotonic=%s, resolution=%s)",
                B.repr({d.adjustable}), B.repr({d.implementation}), B.repr({d.monotonic}), B.repr({d.resolution}))
        end }
    })

    local function bm(name, fn)
        return { __type = "builtin_method", __name = name, __call = fn }
    end

    local Time_dict = {
        _STRUCT_TM_ITEMS = 11,
        timezone = 0,
        altzone = 0,
        daylight = 0,
        tzname = { __type = "tuple", elements = { "UTC", "UTC" } },
        struct_time = struct_time_cls,

        time = bm("time", function() return get_wall_time() end),
        time_ns = bm("time_ns", function()
            local elapsed = ((os.clock() or 0) - base_clock) + virtual_sleep
            local sec_part = base_epoch + math.floor(elapsed)
            local ns_part = math.floor((elapsed - math.floor(elapsed)) * 1e9 + 0.5)
            local big_sec = Evaluator.bigint_op(sec_part, 1000000000, "*")
            return Evaluator.bigint_op(big_sec, ns_part, "+")
        end),

        perf_counter = bm("perf_counter", function() return get_perf_time() end),
        perf_counter_ns = bm("perf_counter_ns", function() return math.floor(get_perf_time() * 1e9 + 0.5) end),

        monotonic = bm("monotonic", function() return get_perf_time() end),
        monotonic_ns = bm("monotonic_ns", function() return math.floor(get_perf_time() * 1e9 + 0.5) end),

        process_time = bm("process_time", function() return get_cpu_time() end),
        process_time_ns = bm("process_time_ns", function() return math.floor(get_cpu_time() * 1e9 + 0.5) end),

        thread_time = bm("thread_time", function() return get_cpu_time() end),
        thread_time_ns = bm("thread_time_ns", function() return math.floor(get_cpu_time() * 1e9 + 0.5) end),

        sleep = bm("sleep", function(a)
            local secs = tonumber(u_num(a[1]))
            if not secs then Runtime.raise("TypeError", "a float is required") end
            if secs < 0 then Runtime.raise("ValueError", "sleep length must be non-negative") end
            virtual_sleep = virtual_sleep + secs
            return B.None
        end),

        gmtime = bm("gmtime", function(a)
            local secs = is_none(a[1]) and get_wall_time() or tonumber(u_num(a[1]))
            if not secs then Runtime.raise("TypeError", "an integer or float is required") end
            return lua_date_to_struct(os.date("!*t", math.floor(secs)), true)
        end),

        localtime = bm("localtime", function(a)
            local secs = is_none(a[1]) and get_wall_time() or tonumber(u_num(a[1]))
            if not secs then Runtime.raise("TypeError", "an integer or float is required") end
            return lua_date_to_struct(os.date("*t", math.floor(secs)), false)
        end),

        mktime = bm("mktime", function(a)
            local r = extract_tuple9(a[1])
            local ts = os.time({
                year = r[1], month = r[2], day = r[3],
                hour = r[4], min = r[5], sec = r[6],
                isdst = (r[9] > 0) and true or ((r[9] == 0) and false or nil)
            })
            if not ts then Runtime.raise("OverflowError", "mktime argument out of range") end
            return ts * 1.0
        end),

        asctime = bm("asctime", function(a)
            return format_asctime(extract_tuple9(a[1]))
        end),

        ctime = bm("ctime", function(a)
            local secs = is_none(a[1]) and get_wall_time() or tonumber(u_num(a[1]))
            if not secs then Runtime.raise("TypeError", "an integer or float is required") end
            local st = lua_date_to_struct(os.date("*t", math.floor(secs)), false)
            return format_asctime(st.__builtin_value.elements)
        end),

        strftime = bm("strftime", function(a)
            local fmt = a[1]
            if type(fmt) ~= "string" then Runtime.raise("TypeError", "strftime() format must be a str") end
            local r = extract_tuple9(a[2])
            local hr12 = r[4] % 12; if hr12 == 0 then hr12 = 12 end
            local map = {
                Y = string.format("%04d", r[1]),
                y = string.format("%02d", r[1] % 100),
                m = string.format("%02d", r[2]),
                d = string.format("%02d", r[3]),
                e = string.format("%2d", r[3]),
                H = string.format("%02d", r[4]),
                I = string.format("%02d", hr12),
                M = string.format("%02d", r[5]),
                S = string.format("%02d", r[6]),
                p = (r[4] < 12) and "AM" or "PM",
                a = wday_abbr[(r[7] % 7) + 1] or "Mon",
                A = wday_full[(r[7] % 7) + 1] or "Monday",
                b = mon_abbr[r[2]] or "Jan",
                B = mon_full[r[2]] or "January",
                j = string.format("%03d", r[8]),
                w = tostring((r[7] + 1) % 7),
                u = tostring((r[7] % 7) + 1),
                Z = "UTC",
                z = "+0000",
                c = format_asctime(r),
                x = string.format("%02d/%02d/%02d", r[2], r[3], r[1] % 100),
                X = string.format("%02d:%02d:%02d", r[4], r[5], r[6]),
                ["%"] = "%"
            }
            return (fmt:gsub("%%(.)", function(ch) return map[ch] or ("%" .. ch) end))
        end),

        strptime = bm("strptime", function(a)
            local data_str = a[1]
            local fmt = a[2] or "%a %b %d %H:%M:%S %Y"
            if type(data_str) ~= "string" or type(fmt) ~= "string" then
                Runtime.raise("TypeError", "strptime() arguments must be str")
            end
            local fields = {}
            local pat = fmt:gsub("([%^%$%(%)%.%[%]%*%+%-%?])", "%%%1")
            pat = pat:gsub("%%%%", "\0PERCENT\0")
            pat = pat:gsub("%%([a-zA-Z])", function(spec)
                table.insert(fields, spec)
                if spec == "Y" then return "(%d%d%d%d)"
                elseif spec == "y" or spec == "m" or spec == "d" or spec == "H" or spec == "I" or spec == "M" or spec == "S" then
                    return "%s*(%d%d?)"
                elseif spec == "j" then return "%s*(%d%d?%d?)"
                elseif spec == "a" or spec == "A" or spec == "b" or spec == "B" or spec == "p" or spec == "Z" then
                    return "(%a+)"
                end
                return "(.-)"
            end)
            pat = "^" .. pat:gsub("%s+", "%%s+"):gsub("%zPERCENT%z", "%%%%") .. "$"
            local caps = { data_str:match(pat) }
            if #caps == 0 and #fields > 0 then
                Runtime.raise("ValueError", "time data '" .. data_str .. "' does not match format '" .. fmt .. "'")
            end
            local year, mon, mday, hour, min, sec, yday = 1900, 1, 1, 0, 0, 0, nil
            local ampm = nil
            for idx, spec in ipairs(fields) do
                local v = caps[idx]
                if spec == "Y" then year = tonumber(v)
                elseif spec == "y" then
                    local yy = tonumber(v)
                    year = (yy <= 68) and (2000 + yy) or (1900 + yy)
                elseif spec == "m" then mon = tonumber(v)
                elseif spec == "d" then mday = tonumber(v)
                elseif spec == "H" then hour = tonumber(v)
                elseif spec == "I" then hour = tonumber(v)
                elseif spec == "M" then min = tonumber(v)
                elseif spec == "S" then sec = tonumber(v)
                elseif spec == "j" then yday = tonumber(v)
                elseif spec == "p" then ampm = v:upper()
                elseif spec == "b" or spec == "B" then
                    local low = v:sub(1, 3):lower()
                    for m_i, m_name in ipairs(mon_abbr) do
                        if m_name:lower() == low then mon = m_i; break end
                    end
                end
            end
            if ampm == "PM" and hour < 12 then hour = hour + 12
            elseif ampm == "AM" and hour == 12 then hour = 0 end

            local ts = os.time({ year = year, month = mon, day = mday, hour = hour, min = min, sec = sec })
            local dt = ts and os.date("!*t", ts) or { wday = 2, yday = yday or 1 }
            local py_wday = ((dt.wday or 2) - 2) % 7
            return make_struct_time({ year, mon, mday, hour, min, sec, py_wday, yday or dt.yday or 1, -1 })
        end),

        get_clock_info = bm("get_clock_info", function(a)
            local name = a[1]
            local valid = {
                time = { adj = true, impl = "os.time()+os.clock()", mono = false, res = 1e-6 },
                perf_counter = { adj = false, impl = "os.clock()", mono = true, res = 1e-6 },
                monotonic = { adj = false, impl = "os.clock()", mono = true, res = 1e-6 },
                process_time = { adj = false, impl = "os.clock()", mono = true, res = 1e-6 },
                thread_time = { adj = false, impl = "os.clock()", mono = true, res = 1e-6 }
            }
            local info = valid[name]
            if not info then Runtime.raise("ValueError", "unknown clock") end
            local inst = PyClass.make_instance(clock_info_cls)
            inst.__dict.adjustable = info.adj
            inst.__dict.implementation = info.impl
            inst.__dict.monotonic = info.mono
            inst.__dict.resolution = info.res
            return inst
        end)
    }

    return require("Module:沙盒/a2569875/PythonScript/Library").make_namespace("time", Time_dict)
end

return DatetimeMod