From 9c13e3f5187ff0c4837ed2a5cf2623f3130419a8 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Sat, 26 Jul 2025 21:21:57 +0800 Subject: [PATCH 1/5] stat: Use uucore::time:format_system_time function --- src/uu/stat/Cargo.toml | 8 +++++++- src/uu/stat/src/stat.rs | 31 +++++++++++++++---------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/uu/stat/Cargo.toml b/src/uu/stat/Cargo.toml index a2eb674b4..cd6f6152e 100644 --- a/src/uu/stat/Cargo.toml +++ b/src/uu/stat/Cargo.toml @@ -19,7 +19,13 @@ path = "src/stat.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true, features = ["entries", "libc", "fs", "fsext"] } +uucore = { workspace = true, features = [ + "entries", + "libc", + "fs", + "fsext", + "time", +] } chrono = { workspace = true } thiserror = { workspace = true } diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 820c99f9b..d0f1c1a6d 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -10,7 +10,8 @@ use clap::builder::ValueParser; use uucore::display::Quotable; use uucore::fs::display_permissions; use uucore::fsext::{ - BirthTime, FsMeta, StatFs, pretty_filetype, pretty_fstype, read_fs_list, statfs, + BirthTime, FsMeta, MetadataTimeField, StatFs, metadata_get_time, pretty_filetype, + pretty_fstype, read_fs_list, statfs, }; use uucore::libc::mode_t; use uucore::{entries, format_usage, show_error, show_warning}; @@ -1026,21 +1027,17 @@ impl Stater { } // time of file birth, human-readable; - if unknown - 'w' => { - OutputType::Str(meta.birth().map_or(String::from("-"), |(sec, nsec)| { - pretty_time(sec as i64, nsec as i64) - })) - } + 'w' => OutputType::Str(pretty_time(meta, MetadataTimeField::Birth)), // time of file birth, seconds since Epoch; 0 if unknown 'W' => OutputType::Unsigned(meta.birth().unwrap_or_default().0), // time of last access, human-readable - 'x' => OutputType::Str(pretty_time(meta.atime(), meta.atime_nsec())), + 'x' => OutputType::Str(pretty_time(meta, MetadataTimeField::Access)), // time of last access, seconds since Epoch 'X' => OutputType::Integer(meta.atime()), // time of last data modification, human-readable - 'y' => OutputType::Str(pretty_time(meta.mtime(), meta.mtime_nsec())), + 'y' => OutputType::Str(pretty_time(meta, MetadataTimeField::Modification)), // time of last data modification, seconds since Epoch 'Y' => { let sec = meta.mtime(); @@ -1060,7 +1057,7 @@ impl Stater { } } // time of last status change, human-readable - 'z' => OutputType::Str(pretty_time(meta.ctime(), meta.ctime_nsec())), + 'z' => OutputType::Str(pretty_time(meta, MetadataTimeField::Change)), // time of last status change, seconds since Epoch 'Z' => OutputType::Integer(meta.ctime()), 'R' => { @@ -1295,14 +1292,16 @@ pub fn uu_app() -> Command { ) } -const PRETTY_DATETIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S.%f %z"; +const PRETTY_DATETIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S.%N %z"; -fn pretty_time(sec: i64, nsec: i64) -> String { - // Return the date in UTC - let tm = DateTime::from_timestamp(sec, nsec as u32).unwrap_or_default(); - let tm: DateTime = tm.into(); - - tm.format(PRETTY_DATETIME_FORMAT).to_string() +fn pretty_time(meta: &Metadata, md_time_field: MetadataTimeField) -> String { + if let Some(time) = metadata_get_time(meta, md_time_field) { + let mut tmp = Vec::new(); + if uucore::time::format_system_time(&mut tmp, time, PRETTY_DATETIME_FORMAT, false).is_ok() { + return String::from_utf8(tmp).unwrap(); + } + } + "-".to_string() } #[cfg(test)] From 6eb75f2d9357cb8dbbf0ac0ca1182a6b270e5c53 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Sat, 26 Jul 2025 21:57:07 +0800 Subject: [PATCH 2/5] stat: Stop relying on fsext::Birth We can rely on other fsext/time functions, and stay a bit more consistent. --- Cargo.lock | 1 - src/uu/stat/Cargo.toml | 1 - src/uu/stat/src/stat.rs | 31 ++++++++++------------------ src/uucore/src/lib/features/fsext.rs | 17 +++------------ src/uucore/src/lib/features/time.rs | 17 ++++++++++----- 5 files changed, 26 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e23074ee5..73272abaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3699,7 +3699,6 @@ dependencies = [ name = "uu_stat" version = "0.1.0" dependencies = [ - "chrono", "clap", "thiserror 2.0.12", "uucore", diff --git a/src/uu/stat/Cargo.toml b/src/uu/stat/Cargo.toml index cd6f6152e..ef77579d4 100644 --- a/src/uu/stat/Cargo.toml +++ b/src/uu/stat/Cargo.toml @@ -26,7 +26,6 @@ uucore = { workspace = true, features = [ "fsext", "time", ] } -chrono = { workspace = true } thiserror = { workspace = true } [features] diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index d0f1c1a6d..8b37779bd 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -10,13 +10,12 @@ use clap::builder::ValueParser; use uucore::display::Quotable; use uucore::fs::display_permissions; use uucore::fsext::{ - BirthTime, FsMeta, MetadataTimeField, StatFs, metadata_get_time, pretty_filetype, - pretty_fstype, read_fs_list, statfs, + FsMeta, MetadataTimeField, StatFs, metadata_get_time, pretty_filetype, pretty_fstype, + read_fs_list, statfs, }; use uucore::libc::mode_t; use uucore::{entries, format_usage, show_error, show_warning}; -use chrono::{DateTime, Local}; use clap::{Arg, ArgAction, ArgMatches, Command}; use std::borrow::Cow; use std::ffi::{OsStr, OsString}; @@ -29,6 +28,7 @@ use std::{env, fs}; use std::collections::HashMap; use thiserror::Error; use uucore::locale::{get_message, get_message_with_args}; +use uucore::time::{format_system_time, system_time_to_sec}; #[derive(Debug, Error)] enum StatError { @@ -1030,7 +1030,10 @@ impl Stater { 'w' => OutputType::Str(pretty_time(meta, MetadataTimeField::Birth)), // time of file birth, seconds since Epoch; 0 if unknown - 'W' => OutputType::Unsigned(meta.birth().unwrap_or_default().0), + 'W' => OutputType::Integer( + metadata_get_time(meta, MetadataTimeField::Birth) + .map_or(0, |x| system_time_to_sec(x).0), + ), // time of last access, human-readable 'x' => OutputType::Str(pretty_time(meta, MetadataTimeField::Access)), @@ -1040,21 +1043,9 @@ impl Stater { 'y' => OutputType::Str(pretty_time(meta, MetadataTimeField::Modification)), // time of last data modification, seconds since Epoch 'Y' => { - let sec = meta.mtime(); - let nsec = meta.mtime_nsec(); - let tm = DateTime::from_timestamp(sec, nsec as u32).unwrap_or_default(); - let tm: DateTime = tm.into(); - match tm.timestamp_nanos_opt() { - None => { - let micros = tm.timestamp_micros(); - let secs = micros as f64 / 1_000_000.0; - OutputType::Float(secs) - } - Some(ns) => { - let secs = ns as f64 / 1_000_000_000.0; - OutputType::Float(secs) - } - } + let (sec, nsec) = metadata_get_time(meta, MetadataTimeField::Modification) + .map_or((0, 0), system_time_to_sec); + OutputType::Float(sec as f64 + nsec as f64 / 1_000_000_000.0) } // time of last status change, human-readable 'z' => OutputType::Str(pretty_time(meta, MetadataTimeField::Change)), @@ -1297,7 +1288,7 @@ const PRETTY_DATETIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S.%N %z"; fn pretty_time(meta: &Metadata, md_time_field: MetadataTimeField) -> String { if let Some(time) = metadata_get_time(meta, md_time_field) { let mut tmp = Vec::new(); - if uucore::time::format_system_time(&mut tmp, time, PRETTY_DATETIME_FORMAT, false).is_ok() { + if format_system_time(&mut tmp, time, PRETTY_DATETIME_FORMAT, false).is_ok() { return String::from_utf8(tmp).unwrap(); } } diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 91e77216a..2d3e9fd0d 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -69,7 +69,9 @@ use std::io::Error as IOError; use std::mem; #[cfg(windows)] use std::path::Path; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::time::SystemTime; +#[cfg(not(windows))] +use std::time::UNIX_EPOCH; use std::{borrow::Cow, ffi::OsString}; use std::fs::Metadata; @@ -114,19 +116,6 @@ pub use libc::statfs as statfs_fn; ))] pub use libc::statvfs as statfs_fn; -pub trait BirthTime { - fn birth(&self) -> Option<(u64, u32)>; -} - -impl BirthTime for Metadata { - fn birth(&self) -> Option<(u64, u32)> { - self.created() - .ok() - .and_then(|t| t.duration_since(UNIX_EPOCH).ok()) - .map(|e| (e.as_secs(), e.subsec_nanos())) - } -} - #[derive(Debug, Copy, Clone)] pub enum MetadataTimeField { Modification, diff --git a/src/uucore/src/lib/features/time.rs b/src/uucore/src/lib/features/time.rs index 8fef2d638..3e7883457 100644 --- a/src/uucore/src/lib/features/time.rs +++ b/src/uucore/src/lib/features/time.rs @@ -25,6 +25,17 @@ fn format_zoned(out: &mut W, zoned: Zoned, fmt: &str) -> UResult<()> { .map_err(|x| USimpleError::new(1, x.to_string())) } +/// Convert a SystemTime` to a number of seconds since UNIX_EPOCH +pub fn system_time_to_sec(time: SystemTime) -> (i64, u32) { + if time > UNIX_EPOCH { + let d = time.duration_since(UNIX_EPOCH).unwrap(); + (d.as_secs() as i64, d.subsec_nanos()) + } else { + let d = UNIX_EPOCH.duration_since(time).unwrap(); + (-(d.as_secs() as i64), d.subsec_nanos()) + } +} + /// Format a `SystemTime` according to given fmt, and append to vector out. pub fn format_system_time( out: &mut W, @@ -42,11 +53,7 @@ pub fn format_system_time( // but it still far enough in the future/past to be unlikely to matter: // jiff: Year between -9999 to 9999 (UTC) [-377705023201..=253402207200] // GNU: Year fits in signed 32 bits (timezone dependent) - let ts: i64 = if time > UNIX_EPOCH { - time.duration_since(UNIX_EPOCH).unwrap().as_secs() as i64 - } else { - -(UNIX_EPOCH.duration_since(time).unwrap().as_secs() as i64) - }; + let ts: i64 = system_time_to_sec(time).0; let str = ts.to_string(); if show_error { show_error!("time '{str}' is out of range"); From 6c73ef622667fa3fe8e6932351eb3b75ec7c8a1d Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Sat, 26 Jul 2025 22:47:02 +0800 Subject: [PATCH 3/5] uucore: time: Add options for format_system_time Depending on the caller, we want the number of seconds, that and an error printed out, or seconds+nanoseconds. --- src/uu/du/src/du.rs | 8 ++- src/uu/ls/src/ls.rs | 3 +- src/uu/stat/src/stat.rs | 11 ++- src/uucore/src/lib/features/time.rs | 100 ++++++++++++++++++++++++---- 4 files changed, 105 insertions(+), 17 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 758d57600..52cd9455b 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -27,6 +27,7 @@ use uucore::locale::{get_message, get_message_with_args}; use uucore::parser::parse_glob; use uucore::parser::parse_size::{ParseSizeError, parse_size_u64}; use uucore::parser::shortcut_value_parser::ShortcutValueParser; +use uucore::time::{FormatSystemTimeFallback, format_system_time}; use uucore::{format_usage, show, show_error, show_warning}; #[cfg(windows)] use windows_sys::Win32::Foundation::HANDLE; @@ -528,7 +529,12 @@ impl StatPrinter { if let Some(md_time) = &self.time { if let Some(time) = metadata_get_time(&stat.metadata, *md_time) { - uucore::time::format_system_time(&mut stdout(), time, &self.time_format, true)?; + format_system_time( + &mut stdout(), + time, + &self.time_format, + FormatSystemTimeFallback::IntegerError, + )?; print!("\t"); } else { print!("???\t"); diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 4bc20e4f5..482847de5 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -60,6 +60,7 @@ use uucore::libc::{dev_t, major, minor}; use uucore::line_ending::LineEnding; use uucore::locale::{get_message, get_message_with_args}; use uucore::quoting_style::{QuotingStyle, locale_aware_escape_dir_name, locale_aware_escape_name}; +use uucore::time::{FormatSystemTimeFallback, format_system_time}; use uucore::{ display::Quotable, error::{UError, UResult, set_exit_code}, @@ -2968,7 +2969,7 @@ fn display_date( _ => &config.time_format_recent, }; - uucore::time::format_system_time(out, time, fmt, false) + format_system_time(out, time, fmt, FormatSystemTimeFallback::Integer) } #[allow(dead_code)] diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 8b37779bd..b25c2624d 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -28,7 +28,7 @@ use std::{env, fs}; use std::collections::HashMap; use thiserror::Error; use uucore::locale::{get_message, get_message_with_args}; -use uucore::time::{format_system_time, system_time_to_sec}; +use uucore::time::{FormatSystemTimeFallback, format_system_time, system_time_to_sec}; #[derive(Debug, Error)] enum StatError { @@ -1288,7 +1288,14 @@ const PRETTY_DATETIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S.%N %z"; fn pretty_time(meta: &Metadata, md_time_field: MetadataTimeField) -> String { if let Some(time) = metadata_get_time(meta, md_time_field) { let mut tmp = Vec::new(); - if format_system_time(&mut tmp, time, PRETTY_DATETIME_FORMAT, false).is_ok() { + if format_system_time( + &mut tmp, + time, + PRETTY_DATETIME_FORMAT, + FormatSystemTimeFallback::Float, + ) + .is_ok() + { return String::from_utf8(tmp).unwrap(); } } diff --git a/src/uucore/src/lib/features/time.rs b/src/uucore/src/lib/features/time.rs index 3e7883457..4f1505b70 100644 --- a/src/uucore/src/lib/features/time.rs +++ b/src/uucore/src/lib/features/time.rs @@ -36,12 +36,19 @@ pub fn system_time_to_sec(time: SystemTime) -> (i64, u32) { } } +/// Sets how `format_system_time` behaves if the time cannot be converted. +pub enum FormatSystemTimeFallback { + Integer, // Just print seconds since epoch (`ls`) + IntegerError, // The above, and print an error (`du``) + Float, // Just print seconds+nanoseconds since epoch (`stat`) +} + /// Format a `SystemTime` according to given fmt, and append to vector out. pub fn format_system_time( out: &mut W, time: SystemTime, fmt: &str, - show_error: bool, + mode: FormatSystemTimeFallback, ) -> UResult<()> { let zoned: Result = time.try_into(); match zoned { @@ -53,12 +60,22 @@ pub fn format_system_time( // but it still far enough in the future/past to be unlikely to matter: // jiff: Year between -9999 to 9999 (UTC) [-377705023201..=253402207200] // GNU: Year fits in signed 32 bits (timezone dependent) - let ts: i64 = system_time_to_sec(time).0; - let str = ts.to_string(); - if show_error { - show_error!("time '{str}' is out of range"); - } - out.write_all(str.as_bytes())?; + let (mut secs, mut nsecs) = system_time_to_sec(time); + match mode { + FormatSystemTimeFallback::Integer => out.write_all(secs.to_string().as_bytes())?, + FormatSystemTimeFallback::IntegerError => { + let str = secs.to_string(); + show_error!("time '{str}' is out of range"); + out.write_all(str.as_bytes())?; + } + FormatSystemTimeFallback::Float => { + if secs < 0 && nsecs != 0 { + secs -= 1; + nsecs = 1_000_000_000 - nsecs; + } + out.write_fmt(format_args!("{secs}.{nsecs:09}"))?; + } + }; Ok(()) } } @@ -66,7 +83,7 @@ pub fn format_system_time( #[cfg(test)] mod tests { - use crate::time::format_system_time; + use crate::time::{FormatSystemTimeFallback, format_system_time}; use std::time::{Duration, UNIX_EPOCH}; // Test epoch SystemTime get printed correctly at UTC0, with 2 simple formats. @@ -76,12 +93,23 @@ mod tests { let time = UNIX_EPOCH; let mut out = Vec::new(); - format_system_time(&mut out, time, "%Y-%m-%d %H:%M", false).expect("Formatting error."); + format_system_time( + &mut out, + time, + "%Y-%m-%d %H:%M", + FormatSystemTimeFallback::Integer, + ) + .expect("Formatting error."); assert_eq!(String::from_utf8(out).unwrap(), "1970-01-01 00:00"); let mut out = Vec::new(); - format_system_time(&mut out, time, "%Y-%m-%d %H:%M:%S.%N %z", false) - .expect("Formatting error."); + format_system_time( + &mut out, + time, + "%Y-%m-%d %H:%M:%S.%N %z", + FormatSystemTimeFallback::Integer, + ) + .expect("Formatting error."); assert_eq!( String::from_utf8(out).unwrap(), "1970-01-01 00:00:00.000000000 +0000" @@ -93,12 +121,58 @@ mod tests { fn test_large_system_time() { let time = UNIX_EPOCH + Duration::from_secs(67_768_036_191_763_200); let mut out = Vec::new(); - format_system_time(&mut out, time, "%Y-%m-%d %H:%M", false).expect("Formatting error."); + format_system_time( + &mut out, + time, + "%Y-%m-%d %H:%M", + FormatSystemTimeFallback::Integer, + ) + .expect("Formatting error."); assert_eq!(String::from_utf8(out).unwrap(), "67768036191763200"); let time = UNIX_EPOCH - Duration::from_secs(67_768_040_922_076_800); let mut out = Vec::new(); - format_system_time(&mut out, time, "%Y-%m-%d %H:%M", false).expect("Formatting error."); + format_system_time( + &mut out, + time, + "%Y-%m-%d %H:%M", + FormatSystemTimeFallback::Integer, + ) + .expect("Formatting error."); assert_eq!(String::from_utf8(out).unwrap(), "-67768040922076800"); } + + // Test that very large (positive or negative) lead to just the timestamp being printed. + #[test] + fn test_large_system_time_float() { + let time = + UNIX_EPOCH + Duration::from_secs(67_768_036_191_763_000) + Duration::from_nanos(123); + let mut out = Vec::new(); + format_system_time( + &mut out, + time, + "%Y-%m-%d %H:%M", + FormatSystemTimeFallback::Float, + ) + .expect("Formatting error."); + assert_eq!( + String::from_utf8(out).unwrap(), + "67768036191763000.000000123" + ); + + let time = + UNIX_EPOCH - Duration::from_secs(67_768_040_922_076_000) + Duration::from_nanos(123); + let mut out = Vec::new(); + format_system_time( + &mut out, + time, + "%Y-%m-%d %H:%M", + FormatSystemTimeFallback::Float, + ) + .expect("Formatting error."); + assert_eq!( + String::from_utf8(out).unwrap(), + "-67768040922076000.000000123" + ); + } } From 84282914612af22923ab10b0fc66673638296291 Mon Sep 17 00:00:00 2001 From: Nicolas Boichat Date: Sat, 26 Jul 2025 23:08:30 +0800 Subject: [PATCH 4/5] fsext: Fix metadata_get_time for change time (ctime) before 1970 Useful for archaeologists and time travellers, do not crash if ctime is before 1970. Creating a file with such a change time is a bit challenging (touch can't change that), so we can't easily add end-to-end tests. Steps to create a filesystem with a file `x` with birth/change time in 1960. ``` dd if=/dev/zero bs=100M count=0 seek=1 of=ext4 mkfs.ext4 ext4 sudo mount ext4 mnt touch mnt/x umount mnt echo "set_inode_field x ctime 196001010101" | debugfs -w ext4 echo "set_inode_field x ctime_extra 1234" | debugfs -w ext4 echo "set_inode_field x crtime 196001010101" | debugfs -w ext4 echo "set_inode_field x crtime_extra 0x123400" | debugfs -w ext4 sudo mount ext4 mnt ``` $ cargo run -p uu_stat mnt/x File: mnt/x size: 0 Blocks: 0 IO Block: 1024 regular empty file Device: 700h/1792d Inode: 13 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2025-07-26 19:52:00.849821694 +0800 Modify: 2025-07-26 19:52:00.849821694 +0800 Change: 1960-01-02 09:01:00.000298240 +0800 Birth: 1960-01-02 09:01:00.000298240 +0800 --- src/uucore/src/lib/features/fsext.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 2d3e9fd0d..4d58a807d 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -142,8 +142,20 @@ impl From<&str> for MetadataTimeField { #[cfg(unix)] fn metadata_get_change_time(md: &Metadata) -> Option { - // TODO: This is incorrect for negative timestamps. - Some(UNIX_EPOCH + Duration::new(md.ctime() as u64, md.ctime_nsec() as u32)) + let mut st = UNIX_EPOCH; + let (secs, nsecs) = (md.ctime(), md.ctime_nsec()); + if secs >= 0 { + st += Duration::from_secs(secs as u64); + } else { + st -= Duration::from_secs(-secs as u64); + } + if nsecs >= 0 { + st += Duration::from_nanos(nsecs as u64); + } else { + // Probably never the case, but cover just in case. + st -= Duration::from_nanos(-nsecs as u64); + } + Some(st) } #[cfg(not(unix))] From a055e856ddc6914b1cffe07cfd6cdb7c7e33910b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 30 Jul 2025 09:51:14 +0200 Subject: [PATCH 5/5] fix rustfmt --- src/uu/stat/src/stat.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index e199d9ce0..ebc50ed0f 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -29,7 +29,6 @@ use std::{env, fs}; use thiserror::Error; use uucore::time::{FormatSystemTimeFallback, format_system_time, system_time_to_sec}; - #[derive(Debug, Error)] enum StatError { #[error("{}", translate!("stat-error-invalid-quoting-style", "style" => style.clone()))]