Skip to main content

deep_time/ymdhms/
mod.rs

1use crate::{ATTOS_PER_SEC_I128, Dt, LiteStr, STRTIME_SIZE, Scale};
2use core::fmt::Write;
3
4#[cfg(any(feature = "jiff-tz-bundle", feature = "jiff-tz"))]
5use crate::{DtErr, DtErrKind, an_err};
6#[cfg(any(feature = "jiff-tz-bundle", feature = "jiff-tz"))]
7use jiff::civil;
8
9/// Combined date + time object.
10///
11/// Has calendar aware and, when the `jiff-tz` feature is enabled,
12/// timezone aware math functions.
13///
14/// ## Examples
15///
16/// **Creating a** [`YmdHms`].
17///
18/// ```rust
19/// use deep_time::{Dt, Scale};
20///
21/// // clamped to 29
22/// let x = Dt::from_ymd(2000, 2, 30, Scale::UTC, 0, 0, 0, 0).to_ymd();
23///
24/// assert_eq!(x.day(), 29);
25/// ```
26///
27/// **Adding a year.** 2000 is a leap year and Feb. 29th is possible, but
28/// 2001 isn't a leap year so the day is clamped to the 28th.
29///
30/// ```rust
31/// use deep_time::{Dt, Scale};
32///
33/// let x = Dt::from_ymd(2000, 2, 29, Scale::UTC, 0, 0, 0, 0).to_ymd();
34/// let x = x.add_years(1);
35///
36/// assert_eq!(x.day(), 28);
37/// ```
38#[derive(Clone, Debug, PartialEq, Eq, Hash)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
41#[cfg_attr(feature = "defmt", derive(defmt::Format))]
42pub struct YmdHms {
43    pub(crate) yr: i64,
44    pub(crate) mo: u8,
45    pub(crate) day: u8,
46    pub(crate) hr: u8,
47    pub(crate) min: u8,
48    pub(crate) sec: u8,    // 0–60 (60 only during leap seconds)
49    pub(crate) attos: u64, // attoseconds (0 ≤ subsec < 10¹⁸)
50    pub(crate) dt: Dt,
51}
52
53impl YmdHms {
54    /// Create a new [`YmdHms`], wrapper around
55    /// [`Dt::from_ymd`](../struct.Dt.html#method.from_ymd).
56    #[inline(always)]
57    pub const fn new(
58        yr: i64,
59        mo: u8,
60        day: u8,
61        scale: Scale,
62        hr: u8,
63        min: u8,
64        sec: u8,
65        attos: u64,
66    ) -> YmdHms {
67        Dt::from_ymd(yr, mo, day, scale, hr, min, sec, attos).to_ymd()
68    }
69
70    /// Returns a copy of the [`YmdHms`] object. Const fn version of
71    /// [`Clone::clone`](core::clone::Clone::clone).
72    #[inline(always)]
73    pub const fn clone_const(&self) -> Self {
74        YmdHms {
75            yr: self.yr,
76            mo: self.mo,
77            day: self.day,
78            hr: self.hr,
79            min: self.min,
80            sec: self.sec,
81            attos: self.attos,
82            dt: self.dt,
83        }
84    }
85
86    /// Returns the [`Dt`] that was used to make this [`YmdHms`] object.
87    #[inline(always)]
88    pub const fn to_dt(&self) -> Dt {
89        self.dt
90    }
91
92    /// Internal helper that round-trips through [`Dt`] to obtain a normalized
93    /// `YmdHms` (handles clamping, leap seconds, etc.).
94    #[inline(always)]
95    const fn reconstruct(
96        &self,
97        yr: i64,
98        mo: u8,
99        day: u8,
100        hr: u8,
101        min: u8,
102        sec: u8,
103        attos: u64,
104    ) -> Self {
105        let mut ymd = Dt::from_ymd(yr, mo, day, self.dt.target, hr, min, sec, attos).to_ymd();
106        ymd.dt.scale = self.dt.scale;
107        ymd
108    }
109
110    /// Adds (or subtracts) whole years, preserving month and day-of-month.
111    /// - Uses standard last-day-of-month clamping.
112    /// - Negative values subtract.
113    pub const fn add_years(&self, n: i64) -> Self {
114        if n == 0 {
115            return self.clone_const();
116        }
117        let new_yr = self.yr.saturating_add(n);
118        let max_day = Dt::days_in_month(new_yr, self.mo);
119        let new_day = Dt::clamp_u8(self.day, 1, max_day);
120        self.reconstruct(
121            new_yr, self.mo, new_day, self.hr, self.min, self.sec, self.attos,
122        )
123    }
124
125    /// Adds (or subtracts) calendar months. Negative values subtract.
126    pub const fn add_months(&self, n: i64) -> Self {
127        if n == 0 {
128            return self.clone_const();
129        }
130
131        let yr = self.yr as i128;
132        let mo = self.mo as i128;
133        let delta = n as i128;
134
135        let total_months = yr * 12 + (mo - 1) + delta;
136
137        let new_yr = Dt::to_i64(total_months.div_euclid(12));
138        let new_mo = Dt::clamp_u8((total_months.rem_euclid(12) + 1) as u8, 1, 12);
139
140        let max_day = Dt::days_in_month(new_yr, new_mo);
141        let new_day = Dt::clamp_u8(self.day, 1, max_day);
142
143        self.reconstruct(
144            new_yr, new_mo, new_day, self.hr, self.min, self.sec, self.attos,
145        )
146    }
147
148    /// Adds (or subtracts) calendar weeks. Negative values subtract.
149    #[inline(always)]
150    pub const fn add_weeks(&self, n: i64) -> Self {
151        self.add_days(n.saturating_mul(7))
152    }
153
154    /// Adds (or subtracts) calendar days. Negative values subtract.
155    pub const fn add_days(&self, n: i64) -> Self {
156        if n == 0 {
157            return self.clone_const();
158        }
159        let jd = Dt::ymd_to_jd(self.yr, self.mo, self.day);
160        let new_jd = jd.saturating_add(n);
161        let (new_yr, new_mo, new_day) = Dt::jd_to_ymd(new_jd);
162        self.reconstruct(
163            new_yr, new_mo, new_day, self.hr, self.min, self.sec, self.attos,
164        )
165    }
166
167    /// Internal implementation detail for all sub-day / physical-time additions.
168    /// Creates a temporary [`Dt`], performs the addition, then converts back to `YmdHms`.
169    const fn _add_attos(&self, attos_delta: i128) -> Self {
170        let tai = Dt::from_ymd(
171            self.yr,
172            self.mo,
173            self.day,
174            self.dt.target,
175            self.hr,
176            self.min,
177            self.sec,
178            self.attos,
179        );
180        let mut ymd = tai.add(Dt::span(attos_delta)).to_ymd();
181        ymd.dt.scale = self.dt.scale;
182        ymd
183    }
184
185    /// Adds (or subtracts) attoseconds. Negative values subtract.
186    #[inline(always)]
187    pub const fn add_attos(&self, n: i128) -> Self {
188        self._add_attos(n)
189    }
190
191    /// Adds (or subtracts) whole seconds. Negative values subtract.
192    #[inline(always)]
193    pub const fn add_sec(&self, n: i64) -> Self {
194        self._add_attos((n as i128).saturating_mul(ATTOS_PER_SEC_I128))
195    }
196
197    /// Adds (or subtracts) whole minutes. Negative values subtract.
198    #[inline]
199    pub const fn add_mins(&self, n: i64) -> Self {
200        let delta = (n as i128)
201            .saturating_mul(60)
202            .saturating_mul(ATTOS_PER_SEC_I128);
203        self._add_attos(delta)
204    }
205
206    /// Adds (or subtracts) whole hours. Negative values subtract.
207    #[inline]
208    pub const fn add_hours(&self, n: i64) -> Self {
209        let delta = (n as i128)
210            .saturating_mul(3600)
211            .saturating_mul(ATTOS_PER_SEC_I128);
212        self._add_attos(delta)
213    }
214
215    /// Returns the year component.
216    #[inline(always)]
217    pub const fn yr(&self) -> i64 {
218        self.yr
219    }
220
221    /// Returns the month component (1–12).
222    #[inline(always)]
223    pub const fn mo(&self) -> u8 {
224        self.mo
225    }
226
227    /// Returns the day-of-month component (1–31, depending on month/year).
228    #[inline(always)]
229    pub const fn day(&self) -> u8 {
230        self.day
231    }
232
233    /// Returns the hour component (0–23).
234    #[inline(always)]
235    pub const fn hr(&self) -> u8 {
236        self.hr
237    }
238
239    /// Returns the minute component (0–59).
240    #[inline(always)]
241    pub const fn min(&self) -> u8 {
242        self.min
243    }
244
245    /// Returns the second component (0–60). The value 60 only occurs during
246    /// a positive leap second on `Scale::UTC` / `UtcSpice` / `UtcHist`.
247    #[inline(always)]
248    pub const fn sec(&self) -> u8 {
249        self.sec
250    }
251
252    /// Returns the attosecond (sub-second) component (0 ≤ attos < 10¹⁸).
253    #[inline(always)]
254    pub const fn attos(&self) -> u64 {
255        self.attos
256    }
257
258    /// The time scale that the object was created on.
259    #[inline(always)]
260    pub const fn time_scale(&self) -> Scale {
261        self.dt.target
262    }
263
264    /// Returns the **ISO week year** (can differ from the calendar year near
265    /// January 1 / December 31).
266    #[inline(always)]
267    pub const fn iso_yr(&self) -> i64 {
268        let (iso_yr, _, _) = Dt::_to_iso_wk_date(self.yr, self.mo, self.day);
269        iso_yr
270    }
271
272    /// Returns the **ISO week number** (1–53). Weeks start on Monday; week 1
273    /// is the week containing the first Thursday of the year.
274    #[inline(always)]
275    pub const fn iso_wk(&self) -> u8 {
276        let (_, iso_wk, _) = Dt::_to_iso_wk_date(self.yr, self.mo, self.day);
277        iso_wk
278    }
279
280    /// Returns the **day of the year** (ordinal date), 1-based (Jan 1 = 1,
281    /// Dec 31 = 365 or 366 in leap years).
282    #[inline(always)]
283    pub const fn day_of_yr(&self) -> u16 {
284        Dt::_day_of_yr(self.yr, self.mo, self.day)
285    }
286
287    /// Returns the **weekday** number according to [`Dt::jd_to_wkday`]
288    /// (0 = Sunday … 6 = Saturday).
289    #[inline(always)]
290    pub const fn wkday(&self) -> u8 {
291        let jd = Dt::ymd_to_jd(self.yr, self.mo, self.day);
292        Dt::jd_to_wkday(jd)
293    }
294
295    /// Returns the **week of year** number when weeks are considered to start
296    /// on Sunday (US-style numbering).
297    #[inline(always)]
298    pub const fn wk_of_yr_sun(&self) -> u8 {
299        Dt::_wk_sun(self.yr, self.day_of_yr())
300    }
301
302    /// Returns the **week of year** number when weeks are considered to start
303    /// on Monday.
304    #[inline(always)]
305    pub const fn wk_of_yr_mon(&self) -> u8 {
306        Dt::_wk_mon(self.yr, self.day_of_yr())
307    }
308
309    /// Formats this value the same way as [`Display`](core::fmt::Display),
310    /// but into a [`LiteStr`](../struct.LiteStr.html).
311    ///
312    /// ## Example
313    ///
314    /// ```rust
315    /// use deep_time::{Dt, Scale};
316    ///
317    /// let ymd = Dt::from_ymd(2000, 1, 2, Scale::UTC, 3, 4, 5, 0).to_ymd();
318    /// assert_eq!(ymd.to_str_lite().as_str(), "2000-01-02T03:04:05 UTC");
319    /// ```
320    #[inline]
321    pub fn to_str_lite(&self) -> LiteStr<STRTIME_SIZE> {
322        let mut s = LiteStr::<STRTIME_SIZE>::default();
323        let _ = write!(s, "{}", self);
324        s
325    }
326}
327
328#[cfg(any(feature = "jiff-tz-bundle", feature = "jiff-tz"))]
329impl YmdHms {
330    /// Adds the given number of years in the specified IANA timezone,
331    /// respecting timezone rules (including DST) and proper calendar arithmetic.
332    ///
333    /// ## Errors
334    ///
335    /// - [`DtErrKind::YearOutOfRange`] if the year of the date is outside the
336    ///   `-9999..=9999` range (checked before involving Jiff).
337    /// - Specific errors for invalid time components when preparing values for Jiff:
338    ///   [`DtErrKind::InvalidHour`], [`DtErrKind::InvalidMinute`],
339    ///   [`DtErrKind::InvalidSecond`], [`DtErrKind::InvalidMonth`], or [`DtErrKind::InvalidDay`].
340    /// - [`DtErrKind::InvalidTimeZone`] if Jiff cannot find/resolve the IANA timezone name.
341    /// - [`DtErrKind::OutOfRange`] if the result of the calendar arithmetic operation
342    ///   would be outside the range supported by Jiff (the checked_add fails).
343    pub fn add_years_tz(&self, n: i64, tz: &str) -> Result<Self, DtErr> {
344        let zoned = self
345            .to_jiff_zoned(tz)?
346            .checked_add(jiff::Span::new().years(n))
347            .map_err(|e| an_err!(DtErrKind::OutOfRange, "{}", e))?;
348        Ok(self.from_jiff_zoned(zoned))
349    }
350
351    /// Adds the given number of months in the specified IANA timezone,
352    /// respecting timezone rules and calendar month-end clamping.
353    ///
354    /// ## Errors
355    ///
356    /// - [`DtErrKind::YearOutOfRange`] if the year of the date is outside the
357    ///   `-9999..=9999` range (checked before involving Jiff).
358    /// - Specific errors for invalid time components when preparing values for Jiff:
359    ///   [`DtErrKind::InvalidHour`], [`DtErrKind::InvalidMinute`],
360    ///   [`DtErrKind::InvalidSecond`], [`DtErrKind::InvalidMonth`], or [`DtErrKind::InvalidDay`].
361    /// - [`DtErrKind::InvalidTimeZone`] if Jiff cannot find/resolve the IANA timezone name.
362    /// - [`DtErrKind::OutOfRange`] if the result of the calendar arithmetic operation
363    ///   would be outside the range supported by Jiff (the checked_add fails).
364    pub fn add_months_tz(&self, n: i64, tz: &str) -> Result<Self, DtErr> {
365        let zoned = self
366            .to_jiff_zoned(tz)?
367            .checked_add(jiff::Span::new().months(n))
368            .map_err(|e| an_err!(DtErrKind::OutOfRange, "{}", e))?;
369        Ok(self.from_jiff_zoned(zoned))
370    }
371
372    /// Adds the given number of weeks in the specified IANA timezone.
373    ///
374    /// ## Errors
375    ///
376    /// - [`DtErrKind::YearOutOfRange`] if the year of the date is outside the
377    ///   `-9999..=9999` range (checked before involving Jiff).
378    /// - Specific errors for invalid time components when preparing values for Jiff:
379    ///   [`DtErrKind::InvalidHour`], [`DtErrKind::InvalidMinute`],
380    ///   [`DtErrKind::InvalidSecond`], [`DtErrKind::InvalidMonth`], or [`DtErrKind::InvalidDay`].
381    /// - [`DtErrKind::InvalidTimeZone`] if Jiff cannot find/resolve the IANA timezone name.
382    /// - [`DtErrKind::OutOfRange`] if the result of the calendar arithmetic operation
383    ///   would be outside the range supported by Jiff (the checked_add fails).
384    #[inline(always)]
385    pub fn add_weeks_tz(&self, n: i64, tz: &str) -> Result<Self, DtErr> {
386        self.add_days_tz(n.saturating_mul(7), tz)
387    }
388
389    /// Adds the given number of calendar days in the specified IANA timezone.
390    ///
391    /// ## Errors
392    ///
393    /// - [`DtErrKind::YearOutOfRange`] if the year of the date is outside the
394    ///   `-9999..=9999` range (checked before involving Jiff).
395    /// - Specific errors for invalid time components when preparing values for Jiff:
396    ///   [`DtErrKind::InvalidHour`], [`DtErrKind::InvalidMinute`],
397    ///   [`DtErrKind::InvalidSecond`], [`DtErrKind::InvalidMonth`], or [`DtErrKind::InvalidDay`].
398    /// - [`DtErrKind::InvalidTimeZone`] if Jiff cannot find/resolve the IANA timezone name.
399    /// - [`DtErrKind::OutOfRange`] if the result of the calendar arithmetic operation
400    ///   would be outside the range supported by Jiff (the checked_add fails).
401    pub fn add_days_tz(&self, n: i64, tz: &str) -> Result<Self, DtErr> {
402        let zoned = self
403            .to_jiff_zoned(tz)?
404            .checked_add(jiff::Span::new().days(n))
405            .map_err(|e| an_err!(DtErrKind::OutOfRange, "{}", e))?;
406        Ok(self.from_jiff_zoned(zoned))
407    }
408
409    /// Adds the given number of hours in the specified IANA timezone,
410    /// respecting timezone rules (including DST).
411    ///
412    /// ## Errors
413    ///
414    /// - [`DtErrKind::YearOutOfRange`] if the year of the date is outside the
415    ///   `-9999..=9999` range (checked before involving Jiff).
416    /// - Specific errors for invalid time components when preparing values for Jiff:
417    ///   [`DtErrKind::InvalidHour`], [`DtErrKind::InvalidMinute`],
418    ///   [`DtErrKind::InvalidSecond`], [`DtErrKind::InvalidMonth`], or [`DtErrKind::InvalidDay`].
419    /// - [`DtErrKind::InvalidTimeZone`] if Jiff cannot find/resolve the IANA timezone name.
420    /// - [`DtErrKind::OutOfRange`] if the result of the calendar arithmetic operation
421    ///   would be outside the range supported by Jiff (the checked_add fails).
422    pub fn add_hours_tz(&self, n: i64, tz: &str) -> Result<Self, DtErr> {
423        let new_zoned = self
424            .to_jiff_zoned(tz)?
425            .checked_add(jiff::Span::new().hours(n))
426            .map_err(|e| an_err!(DtErrKind::OutOfRange, "{}", e))?;
427        Ok(self.from_jiff_zoned(new_zoned))
428    }
429
430    /// Adds the given number of minutes in the specified IANA timezone,
431    /// respecting timezone rules (including DST).
432    ///
433    /// ## Errors
434    ///
435    /// - [`DtErrKind::YearOutOfRange`] if the year of the date is outside the
436    ///   `-9999..=9999` range (checked before involving Jiff).
437    /// - Specific errors for invalid time components when preparing values for Jiff:
438    ///   [`DtErrKind::InvalidHour`], [`DtErrKind::InvalidMinute`],
439    ///   [`DtErrKind::InvalidSecond`], [`DtErrKind::InvalidMonth`], or [`DtErrKind::InvalidDay`].
440    /// - [`DtErrKind::InvalidTimeZone`] if Jiff cannot find/resolve the IANA timezone name.
441    /// - [`DtErrKind::OutOfRange`] if the result of the calendar arithmetic operation
442    ///   would be outside the range supported by Jiff (the checked_add fails).
443    pub fn add_mins_tz(&self, n: i64, tz: &str) -> Result<Self, DtErr> {
444        let zoned = self
445            .to_jiff_zoned(tz)?
446            .checked_add(jiff::Span::new().minutes(n))
447            .map_err(|e| an_err!(DtErrKind::OutOfRange, "{}", e))?;
448        Ok(self.from_jiff_zoned(zoned))
449    }
450
451    /// Adds the given number of seconds in the specified IANA timezone.
452    ///
453    /// ## Errors
454    ///
455    /// - [`DtErrKind::YearOutOfRange`] if the year of the date is outside the
456    ///   `-9999..=9999` range (checked before involving Jiff).
457    /// - Specific errors for invalid time components when preparing values for Jiff:
458    ///   [`DtErrKind::InvalidHour`], [`DtErrKind::InvalidMinute`],
459    ///   [`DtErrKind::InvalidSecond`], [`DtErrKind::InvalidMonth`], or [`DtErrKind::InvalidDay`].
460    /// - [`DtErrKind::InvalidTimeZone`] if Jiff cannot find/resolve the IANA timezone name.
461    /// - [`DtErrKind::OutOfRange`] if the result of the calendar arithmetic operation
462    ///   would be outside the range supported by Jiff (the checked_add fails).
463    pub fn add_sec_tz(&self, n: i64, tz: &str) -> Result<Self, DtErr> {
464        let zoned = self
465            .to_jiff_zoned(tz)?
466            .checked_add(jiff::Span::new().seconds(n))
467            .map_err(|e| an_err!(DtErrKind::OutOfRange, "{}", e))?;
468        Ok(self.from_jiff_zoned(zoned))
469    }
470
471    // helpers
472
473    fn to_jiff_zoned(&self, tz: &str) -> Result<jiff::Zoned, DtErr> {
474        if !(-9999..=9999).contains(&self.yr) {
475            return Err(an_err!(DtErrKind::YearOutOfRange));
476        }
477
478        let hr: i8 = self
479            .hr
480            .try_into()
481            .map_err(|_| an_err!(DtErrKind::InvalidHour))?;
482        let min: i8 = self
483            .min
484            .try_into()
485            .map_err(|_| an_err!(DtErrKind::InvalidMinute))?;
486
487        let sec_for_jiff: i8 = if self.sec == 60 {
488            59
489        } else {
490            self.sec
491                .try_into()
492                .map_err(|_| an_err!(DtErrKind::InvalidSecond))?
493        };
494
495        let mo: i8 = self
496            .mo
497            .try_into()
498            .map_err(|_| an_err!(DtErrKind::InvalidMonth))?;
499        let day: i8 = self
500            .day
501            .try_into()
502            .map_err(|_| an_err!(DtErrKind::InvalidDay))?;
503
504        let civil_time = civil::date(self.yr as i16, mo, day).at(hr, min, sec_for_jiff, 0);
505
506        civil_time
507            .in_tz(tz)
508            .map_err(|e| an_err!(DtErrKind::InvalidTimeZone, "{}", e))
509    }
510
511    fn from_jiff_zoned(&self, zoned: jiff::Zoned) -> Self {
512        let civil = zoned.datetime();
513
514        self.reconstruct(
515            civil.year() as i64,
516            civil.month() as u8,
517            civil.day() as u8,
518            civil.hour() as u8,
519            civil.minute() as u8,
520            civil.second() as u8,
521            self.attos,
522        )
523    }
524}
525
526impl core::fmt::Display for YmdHms {
527    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
528        // Year: 4-digit padded when |yr| < 10000, natural width otherwise
529        if self.yr >= 0 {
530            if self.yr < 10000 {
531                core::write!(f, "{:04}", self.yr)?;
532            } else {
533                core::write!(f, "{}", self.yr)?;
534            }
535        } else {
536            let abs = (-self.yr) as u64;
537            if abs < 10000 {
538                core::write!(f, "-{:04}", abs)?;
539            } else {
540                core::write!(f, "-{}", abs)?;
541            }
542        }
543
544        // Month (pad only if < 10)
545        if self.mo < 10 {
546            core::write!(f, "-0{}", self.mo)?;
547        } else {
548            core::write!(f, "-{}", self.mo)?;
549        }
550
551        // Day (pad only if < 10)
552        if self.day < 10 {
553            core::write!(f, "-0{}", self.day)?;
554        } else {
555            core::write!(f, "-{}", self.day)?;
556        }
557
558        core::write!(f, "T")?;
559
560        // Hour (pad only if < 10)
561        if self.hr < 10 {
562            core::write!(f, "0{}", self.hr)?;
563        } else {
564            core::write!(f, "{}", self.hr)?;
565        }
566
567        core::write!(f, ":")?;
568
569        // Minute (pad only if < 10)
570        if self.min < 10 {
571            core::write!(f, "0{}", self.min)?;
572        } else {
573            core::write!(f, "{}", self.min)?;
574        }
575
576        core::write!(f, ":")?;
577
578        // Second (pad only if < 10) — 60 is still fine
579        if self.sec < 10 {
580            core::write!(f, "0{}", self.sec)?;
581        } else {
582            core::write!(f, "{}", self.sec)?;
583        }
584
585        // Fractional attoseconds
586        if self.attos != 0 {
587            let mut buf = [0u8; 18];
588            let mut n = self.attos;
589            for i in (0..18).rev() {
590                buf[i] = (n % 10) as u8 + b'0';
591                n /= 10;
592            }
593            let mut end = 18;
594            while end > 0 && buf[end - 1] == b'0' {
595                end -= 1;
596            }
597
598            core::write!(f, ".")?;
599            for &byte in &buf[..end] {
600                core::write!(f, "{}", byte as char)?;
601            }
602        }
603
604        // Scale abbreviation at the end
605        core::write!(f, " {}", self.dt.target.abbrev())
606    }
607}