Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

4982 - Implement tm_gmtoff and tm_zone #5391

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions 15 Lib/test/test_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,9 +718,10 @@ class TestAsctime4dyear(_TestAsctimeYear, _Test4dYear, unittest.TestCase):


class TestPytime(unittest.TestCase):
# TODO: RUSTPYTHON
@unittest.expectedFailure
@skip_if_buggy_ucrt_strfptime
@unittest.skip("TODO: RUSTPYTHON, AttributeError: module 'time' has no attribute '_STRUCT_TM_ITEMS'")
# @unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support")
@unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support")
def test_localtime_timezone(self):

# Get the localtime and examine it for the offset and zone.
Expand Down Expand Up @@ -755,16 +756,18 @@ def test_localtime_timezone(self):
self.assertEqual(new_lt.tm_gmtoff, lt.tm_gmtoff)
self.assertEqual(new_lt9.tm_zone, lt.tm_zone)

@unittest.skip("TODO: RUSTPYTHON, AttributeError: module 'time' has no attribute '_STRUCT_TM_ITEMS'")
# @unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support")
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support")
def test_strptime_timezone(self):
t = time.strptime("UTC", "%Z")
self.assertEqual(t.tm_zone, 'UTC')
t = time.strptime("+0500", "%z")
self.assertEqual(t.tm_gmtoff, 5 * 3600)

@unittest.skip("TODO: RUSTPYTHON, AttributeError: module 'time' has no attribute '_STRUCT_TM_ITEMS'")
# @unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support")
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support")
def test_short_times(self):

import pickle
Expand Down
30 changes: 15 additions & 15 deletions 30 derive-impl/src/pystructseq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,19 @@ fn field_names(input: &mut DeriveInput) -> Result<(Vec<Ident>, Vec<Ident>)> {
}

pub(crate) fn impl_pystruct_sequence(mut input: DeriveInput) -> Result<TokenStream> {
let (not_skipped_fields, _skipped_fields) = field_names(&mut input)?;
let (not_skipped_fields, skipped_fields) = field_names(&mut input)?;
let ty = &input.ident;
let ret = quote! {
impl ::rustpython_vm::types::PyStructSequence for #ty {
const FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#not_skipped_fields)),*];
const REQUIRED_FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#not_skipped_fields),)*];
const OPTIONAL_FIELD_NAMES: &'static [&'static str] = &[#(stringify!(#skipped_fields),)*];
fn into_tuple(self, vm: &::rustpython_vm::VirtualMachine) -> ::rustpython_vm::builtins::PyTuple {
let items = vec![#(::rustpython_vm::convert::ToPyObject::to_pyobject(
self.#not_skipped_fields,
vm,
)),*];
let items = vec![
#(::rustpython_vm::convert::ToPyObject::to_pyobject(
self.#not_skipped_fields,
vm,
),)*
];
::rustpython_vm::builtins::PyTuple::new_unchecked(items.into_boxed_slice())
}
}
Expand All @@ -110,17 +113,14 @@ pub(crate) fn impl_pystruct_sequence_try_from_object(
let ret = quote! {
impl ::rustpython_vm::TryFromObject for #ty {
fn try_from_object(vm: &::rustpython_vm::VirtualMachine, seq: ::rustpython_vm::PyObjectRef) -> ::rustpython_vm::PyResult<Self> {
const LEN: usize = #ty::FIELD_NAMES.len();
let seq = Self::try_elements_from::<LEN>(seq, vm)?;
// TODO: this is possible to be written without iterator
let seq = Self::try_elements_from(seq, vm)?;
let mut iter = seq.into_iter();
Ok(Self {
#(
#not_skipped_fields: iter.next().unwrap().clone().try_into_value(vm)?,
)*
#(
#skipped_fields: vm.ctx.none(),
)*
#(#not_skipped_fields: iter.next().unwrap().clone().try_into_value(vm)?,)*
#(#skipped_fields: match iter.next() {
Some(v) => v.clone().try_into_value(vm)?,
None => vm.ctx.none(),
},)*
})
}
}
Expand Down
16 changes: 15 additions & 1 deletion 16 vm/src/stdlib/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ mod decl {
types::PyStructSequence,
};
use chrono::{
DateTime, Datelike, Timelike,
DateTime, Datelike, TimeZone, Timelike,
naive::{NaiveDate, NaiveDateTime, NaiveTime},
};
use std::time::Duration;
Expand Down Expand Up @@ -72,6 +72,9 @@ mod decl {
.map_err(|e| vm.new_value_error(format!("Time error: {e:?}")))
}

#[pyattr]
pub const _STRUCT_TM_ITEMS: usize = 11;

// TODO: implement proper monotonic time for wasm/wasi.
#[cfg(not(any(unix, windows)))]
fn get_monotonic_time(vm: &VirtualMachine) -> PyResult<Duration> {
Expand Down Expand Up @@ -451,6 +454,10 @@ mod decl {
tm_wday: PyObjectRef,
tm_yday: PyObjectRef,
tm_isdst: PyObjectRef,
#[pystruct(skip)]
tm_gmtoff: PyObjectRef,
#[pystruct(skip)]
tm_zone: PyObjectRef,
}

impl std::fmt::Debug for PyStructTime {
Expand All @@ -462,6 +469,11 @@ mod decl {
#[pyclass(with(PyStructSequence))]
impl PyStructTime {
fn new(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self {
let local_time = chrono::Local.from_local_datetime(&tm).unwrap();
let offset_seconds =
local_time.offset().local_minus_utc() + if isdst == 1 { 3600 } else { 0 };
let tz_abbr = local_time.format("%Z").to_string();

PyStructTime {
tm_year: vm.ctx.new_int(tm.year()).into(),
tm_mon: vm.ctx.new_int(tm.month()).into(),
Expand All @@ -472,6 +484,8 @@ mod decl {
tm_wday: vm.ctx.new_int(tm.weekday().num_days_from_monday()).into(),
tm_yday: vm.ctx.new_int(tm.ordinal()).into(),
tm_isdst: vm.ctx.new_int(isdst).into(),
tm_gmtoff: vm.ctx.new_int(offset_seconds).into(),
tm_zone: vm.ctx.new_str(tz_abbr).into(),
}
}

Expand Down
43 changes: 25 additions & 18 deletions 43 vm/src/types/structseq.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use crate::{
AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
builtins::{PyTuple, PyTupleRef, PyType},
builtins::{PyBaseExceptionRef, PyTuple, PyTupleRef, PyType},
class::{PyClassImpl, StaticType},
vm::Context,
};

#[pyclass]
pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static {
const FIELD_NAMES: &'static [&'static str];
const REQUIRED_FIELD_NAMES: &'static [&'static str];
const OPTIONAL_FIELD_NAMES: &'static [&'static str];

fn into_tuple(self, vm: &VirtualMachine) -> PyTuple;

Expand All @@ -17,10 +18,16 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static {
.unwrap()
}

fn try_elements_from<const FIELD_LEN: usize>(
obj: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult<[PyObjectRef; FIELD_LEN]> {
fn try_elements_from(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
#[cold]
fn sequence_length_error(
name: &str,
len: usize,
vm: &VirtualMachine,
) -> PyBaseExceptionRef {
vm.new_type_error(format!("{name} takes a sequence of length {len}"))
}

let typ = Self::static_type();
// if !obj.fast_isinstance(typ) {
// return Err(vm.new_type_error(format!(
Expand All @@ -30,13 +37,13 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static {
// )));
// }
let seq: Vec<PyObjectRef> = obj.try_into_value(vm)?;
let seq: [PyObjectRef; FIELD_LEN] = seq.try_into().map_err(|_| {
vm.new_type_error(format!(
"{} takes a sequence of length {}",
typ.name(),
FIELD_LEN
))
})?;
if seq.len() < Self::REQUIRED_FIELD_NAMES.len() {
return Err(sequence_length_error(
&typ.name(),
Self::REQUIRED_FIELD_NAMES.len(),
vm,
));
}
Ok(seq)
}

Expand All @@ -49,14 +56,14 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static {
let (body, suffix) = if let Some(_guard) =
rustpython_vm::recursion::ReprGuard::enter(vm, zelf.as_object())
{
if Self::FIELD_NAMES.len() == 1 {
if Self::REQUIRED_FIELD_NAMES.len() == 1 {
let value = zelf.first().unwrap();
let formatted = format_field((value, Self::FIELD_NAMES[0]))?;
let formatted = format_field((value, Self::REQUIRED_FIELD_NAMES[0]))?;
(formatted, ",")
} else {
let fields: PyResult<Vec<_>> = zelf
.iter()
.zip(Self::FIELD_NAMES.iter().copied())
.zip(Self::REQUIRED_FIELD_NAMES.iter().copied())
.map(format_field)
.collect();
(fields?.join(", "), "")
Expand All @@ -74,7 +81,7 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static {

#[extend_class]
fn extend_pyclass(ctx: &Context, class: &'static Py<PyType>) {
for (i, &name) in Self::FIELD_NAMES.iter().enumerate() {
for (i, &name) in Self::REQUIRED_FIELD_NAMES.iter().enumerate() {
// cast i to a u8 so there's less to store in the getter closure.
// Hopefully there's not struct sequences with >=256 elements :P
let i = i as u8;
Expand All @@ -90,7 +97,7 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static {
class.set_attr(
identifier!(ctx, __match_args__),
ctx.new_tuple(
Self::FIELD_NAMES
Self::REQUIRED_FIELD_NAMES
.iter()
.map(|&name| ctx.new_str(name).into())
.collect::<Vec<_>>(),
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.