模組:沙盒/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