mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Add a native _zoneinfo module (#8915)
* Add a native _zoneinfo module ZoneInfo reads TZif tuples from zoneinfo._common.load_data. The stdlib module implements utcoffset, dst, tzname, fromutc, and the zone cache. whats_left compares that class. Cache tests no longer expect failure. Assisted-by: Grok:grok-4.7 * Fix ZoneInfo layout and keep its cache on the module utcoffset, dst, and tzname take the object. Zone data is boxed so the payload lines up with tzinfo on 32-bit. The strong, weak, and timedelta caches live on each interpreter's _zoneinfo module. Assisted-by: Grok:grok-4.7
This commit is contained in:
1 parent
ab3d759e74
commit
cb0f3b012b
6 files changed
+1479
-23
No files matched your search
-4
@@ -1609,7 +1609,6 @@ class ZoneInfoCacheTest(TzPathUserMixin, ZoneInfoTestBase):
|
||||
class CZoneInfoCacheTest(ZoneInfoCacheTest):
|
||||
module = c_zoneinfo
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: RuntimeError not raised
|
||||
def test_inconsistent_weak_cache_get(self):
|
||||
class Cache:
|
||||
def get(self, key, default=None):
|
||||
@@ -1628,7 +1627,6 @@ class CZoneInfoCacheTest(ZoneInfoCacheTest):
|
||||
"Unexpected instance of int in ZI weak cache for key 'America/Los_Angeles'"
|
||||
)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: AttributeError not raised
|
||||
def test_deleted_weak_cache(self):
|
||||
class ZI(self.klass):
|
||||
pass
|
||||
@@ -1641,7 +1639,6 @@ class CZoneInfoCacheTest(ZoneInfoCacheTest):
|
||||
with self.assertRaises(AttributeError):
|
||||
ZI.clear_cache()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'int' object has no attribute '_from_cache'
|
||||
def test_inconsistent_weak_cache_setdefault(self):
|
||||
class Cache:
|
||||
def get(self, key, default=None):
|
||||
@@ -2092,7 +2089,6 @@ class ExtensionBuiltTest(unittest.TestCase):
|
||||
rely on these tests as an indication of stable properties of these classes.
|
||||
"""
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: type object 'ZoneInfo' has unexpected attribute '_weak_cache'
|
||||
def test_cache_location(self):
|
||||
# The pure Python version stores caches on attributes, but the C
|
||||
# extension stores them in C globals (at least for now)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// cspell:ignore ymd ordinal isocalendar ISO fromisoformat
|
||||
// cspell:ignore dnum zreplacement colonzreplacement zname freplacement somezreplacement
|
||||
// cspell:ignore tzoffset tzmicrosecond tzsign dtstr nogo dtobj tzusec timet
|
||||
pub(crate) use _datetime::module_def;
|
||||
pub(crate) use _datetime::{PyTzInfo, datetime_type, module_def, timedelta_from_seconds};
|
||||
|
||||
#[pymodule]
|
||||
mod _datetime {
|
||||
@@ -940,10 +940,18 @@ mod _datetime {
|
||||
PyDate::static_type()
|
||||
}
|
||||
|
||||
fn datetime_type() -> &'static Py<PyType> {
|
||||
pub(crate) fn datetime_type() -> &'static Py<PyType> {
|
||||
PyDateTime::static_type()
|
||||
}
|
||||
|
||||
pub(crate) fn timedelta_from_seconds(
|
||||
seconds: i64,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyObjectRef> {
|
||||
let delta = PyDelta::new_ex(0, seconds, 0, true, vm)?;
|
||||
Ok(delta.into_ref(&vm.ctx).into())
|
||||
}
|
||||
|
||||
fn time_type() -> &'static Py<PyType> {
|
||||
PyTime::static_type()
|
||||
}
|
||||
@@ -2331,7 +2339,7 @@ mod _datetime {
|
||||
#[pyattr]
|
||||
#[pyclass(module = "datetime", name = "tzinfo")]
|
||||
#[derive(Debug, Default, PyPayload)]
|
||||
pub(super) struct PyTzInfo {}
|
||||
pub(crate) struct PyTzInfo {}
|
||||
|
||||
impl Constructor for PyTzInfo {
|
||||
type Args = FuncArgs;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -57,6 +57,7 @@ mod mmap;
|
||||
mod _datetime;
|
||||
mod _heapq;
|
||||
mod _queue;
|
||||
mod _zoneinfo;
|
||||
mod pickle;
|
||||
mod pyexpat;
|
||||
mod pystruct;
|
||||
@@ -259,6 +260,7 @@ pub fn stdlib_module_defs(ctx: &Context) -> Vec<&'static builtins::PyModuleDef>
|
||||
pyexpat::module_def(ctx),
|
||||
pystruct::module_def(ctx),
|
||||
_datetime::module_def(ctx),
|
||||
_zoneinfo::module_def(ctx),
|
||||
_heapq::module_def(ctx),
|
||||
_queue::module_def(ctx),
|
||||
random::module_def(ctx),
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from zoneinfo import ZoneInfo, reset_tzpath
|
||||
|
||||
import _interpreters
|
||||
|
||||
# Fixed-offset UTC TZif, so the cache check does not need system zone data.
|
||||
UTC = b"TZif2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00UTC\x00TZif2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00UTC\x00\nUTC0\n"
|
||||
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
Path(directory, "UTC").write_bytes(UTC)
|
||||
reset_tzpath([directory])
|
||||
main = ZoneInfo("UTC")
|
||||
assert main is ZoneInfo("UTC")
|
||||
|
||||
interp = _interpreters.create()
|
||||
try:
|
||||
result = _interpreters.run_string(
|
||||
interp,
|
||||
"""
|
||||
from zoneinfo import ZoneInfo, reset_tzpath
|
||||
reset_tzpath([tzpath])
|
||||
first = ZoneInfo("UTC")
|
||||
second = ZoneInfo("UTC")
|
||||
assert first is second
|
||||
assert id(first) != main_id
|
||||
ZoneInfo.clear_cache()
|
||||
""",
|
||||
{"tzpath": directory, "main_id": id(main)},
|
||||
)
|
||||
finally:
|
||||
_interpreters.destroy(interp)
|
||||
|
||||
if result is not None:
|
||||
detail = getattr(result, "formatted", None) or repr(result)
|
||||
raise AssertionError(detail)
|
||||
assert main is ZoneInfo("UTC")
|
||||
@@ -414,20 +414,6 @@ def _pure_decimal_baseline(wrapper):
|
||||
return result
|
||||
|
||||
|
||||
def _use_pure_zoneinfo(dir_result):
|
||||
"""Replace the accelerated ZoneInfo with the pure-Python class."""
|
||||
import zoneinfo._zoneinfo as pure_zoneinfo
|
||||
|
||||
cls = pure_zoneinfo.ZoneInfo
|
||||
name = cls.__name__
|
||||
for key in list(dir_result):
|
||||
if key == name or key.startswith(f"{name}."):
|
||||
del dir_result[key]
|
||||
dir_result[name] = extra_info(cls)
|
||||
for attr, info in own_attrs(cls):
|
||||
dir_result[f"{name}.{attr}"] = info
|
||||
|
||||
|
||||
def gen_modules():
|
||||
# check name because modules listed have side effects on import,
|
||||
# e.g. printing something or opening a webpage
|
||||
@@ -465,8 +451,6 @@ def gen_modules():
|
||||
):
|
||||
for attr, info in own_attrs(item):
|
||||
dir_result[f"{item_name}.{attr}"] = info
|
||||
if mod_name == "zoneinfo":
|
||||
_use_pure_zoneinfo(dir_result)
|
||||
modules[mod_name] = dir_result
|
||||
return modules
|
||||
|
||||
|
||||
Reference in new issue
Block a user