mysql_common/binlog/
time.rs

1use std::{
2    cmp::min,
3    fmt::{self, Write},
4};
5
6use super::misc::{my_packed_time_get_frac_part, my_packed_time_get_int_part};
7
8/// Server-side mysql time representation.
9#[derive(Debug, Clone, PartialEq)]
10#[repr(C)]
11pub struct MysqlTime {
12    pub year: u32,
13    pub month: u32,
14    pub day: u32,
15    pub hour: u32,
16    pub minute: u32,
17    pub second: u32,
18    /// microseconds
19    pub second_part: u32,
20    pub neg: bool,
21    pub time_type: MysqlTimestampType,
22    pub time_zone_displacement: i32,
23}
24
25impl MysqlTime {
26    /// Convert time packed numeric representation to [`MysqlTime`].
27    pub fn from_int64_time_packed(mut packed_value: i64) -> Self {
28        let neg = packed_value < 0;
29        if neg {
30            packed_value = -packed_value
31        }
32
33        let hms: i64 = my_packed_time_get_int_part(packed_value);
34
35        let hour = (hms >> 12) as u32 % (1 << 10); /* 10 bits starting at 12th */
36        let minute = (hms >> 6) as u32 % (1 << 6); /* 6 bits starting at 6th   */
37        let second = hms as u32 % (1 << 6); /* 6 bits starting at 0th   */
38        let second_part = my_packed_time_get_frac_part(packed_value);
39
40        Self {
41            year: 0,
42            month: 0,
43            day: 0,
44            hour,
45            minute,
46            second,
47            second_part: second_part as u32,
48            neg,
49            time_type: MysqlTimestampType::MYSQL_TIMESTAMP_TIME,
50            time_zone_displacement: 0,
51        }
52    }
53
54    /// Convert packed numeric date representation to [`MysqlTime`].
55    pub fn from_int64_date_packed(packed_value: i64) -> Self {
56        let mut this = Self::from_int64_datetime_packed(packed_value);
57        this.time_type = MysqlTimestampType::MYSQL_TIMESTAMP_DATE;
58        this
59    }
60
61    /// Convert packed numeric datetime representation to [`MysqlTime`].
62    pub fn from_int64_datetime_packed(mut packed_value: i64) -> Self {
63        let neg = packed_value < 0;
64        if neg {
65            packed_value = -packed_value
66        }
67
68        let second_part = my_packed_time_get_frac_part(packed_value);
69        let ymdhms: i64 = my_packed_time_get_int_part(packed_value);
70
71        let ymd: i64 = ymdhms >> 17;
72        let ym: i64 = ymd >> 5;
73        let hms: i64 = ymdhms % (1 << 17);
74
75        let day = ymd % (1 << 5);
76        let month = ym % 13;
77        let year = (ym / 13) as _;
78
79        let second = hms % (1 << 6);
80        let minute = (hms >> 6) % (1 << 6);
81        let hour = (hms >> 12) as _;
82
83        Self {
84            year,
85            month: month as _,
86            day: day as _,
87            hour,
88            minute: minute as _,
89            second: second as _,
90            second_part: second_part as _,
91            neg,
92            time_type: MysqlTimestampType::MYSQL_TIMESTAMP_DATETIME,
93            time_zone_displacement: 0,
94        }
95    }
96}
97
98impl fmt::Display for MysqlTime {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        match self.time_type {
101            MysqlTimestampType::MYSQL_TIMESTAMP_DATETIME
102            | MysqlTimestampType::MYSQL_TIMESTAMP_DATETIME_TZ => format_datetime(self, f),
103            MysqlTimestampType::MYSQL_TIMESTAMP_DATE => format_date(self, f),
104            MysqlTimestampType::MYSQL_TIMESTAMP_TIME => format_time(self, f),
105            MysqlTimestampType::MYSQL_TIMESTAMP_NONE
106            | MysqlTimestampType::MYSQL_TIMESTAMP_ERROR => Ok(()),
107        }
108    }
109}
110
111fn trim_two_digits(value: u32) -> u32 {
112    if value >= 100 { 0 } else { value }
113}
114
115/// Formats a time value as `HH:MM:SS[.fraction]`.
116fn format_time(time: &MysqlTime, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117    if time.neg {
118        f.write_char('-')?;
119    }
120
121    write!(
122        f,
123        "{:02}:{:02}:{:02}",
124        time.hour,
125        trim_two_digits(time.minute),
126        trim_two_digits(time.second),
127    )?;
128    format_useconds(time.second_part, f)?;
129    Ok(())
130}
131
132/// Formats a datetime value with an optional fractional part (if formatter precision is given).
133fn format_datetime(time: &MysqlTime, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134    format_date_and_time(time, f)?;
135    format_useconds(time.second_part, f)?;
136    if time.time_type == MysqlTimestampType::MYSQL_TIMESTAMP_DATETIME_TZ {
137        format_tz(time.time_zone_displacement, f)?;
138    }
139    Ok(())
140}
141
142/// Formats date and time part as 'YYYY-MM-DD hh:mm:ss'
143fn format_date_and_time(time: &MysqlTime, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144    write!(
145        f,
146        "{:02}{:02}-{:02}-{:02} {:02}:{:02}:{:02}",
147        trim_two_digits(time.year / 100),
148        trim_two_digits(time.year % 100),
149        trim_two_digits(time.month),
150        trim_two_digits(time.day),
151        trim_two_digits(time.hour),
152        trim_two_digits(time.minute),
153        trim_two_digits(time.second),
154    )
155}
156
157/// Formats a date value as 'YYYY-MM-DD'.
158fn format_date(time: &MysqlTime, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159    write!(
160        f,
161        "{:02}{:02}-{:02}-{:02}",
162        trim_two_digits(time.year / 100),
163        trim_two_digits(time.year % 100),
164        trim_two_digits(time.month),
165        trim_two_digits(time.day),
166    )
167}
168
169/// Only formats useconds if formatter precision is given (will be truncated to 6)
170fn format_useconds(mut useconds: u32, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171    let Some(dec) = f.precision().map(|x| min(x, 6)) else {
172        return Ok(());
173    };
174
175    if dec == 0 {
176        return Ok(());
177    }
178
179    useconds %= 1_000_000;
180
181    for _ in 0..(6 - dec) {
182        useconds /= 10;
183    }
184
185    write!(f, ".{:0width$}", useconds, width = dec)
186}
187
188fn format_tz(tzd: i32, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189    write!(f, "+{:02}:{:02}", tzd / 3600, tzd.abs() / 60 % 60)
190}
191
192#[derive(Debug, Clone, PartialEq)]
193#[repr(C)]
194#[allow(non_camel_case_types)]
195pub enum MysqlTimestampType {
196    /// Textual representation of this value is an empty string
197    MYSQL_TIMESTAMP_NONE = -2,
198    /// Textual representation of this value is an empty string
199    MYSQL_TIMESTAMP_ERROR = -1,
200    MYSQL_TIMESTAMP_DATE = 0,
201    MYSQL_TIMESTAMP_DATETIME = 1,
202    MYSQL_TIMESTAMP_TIME = 2,
203    MYSQL_TIMESTAMP_DATETIME_TZ = 3,
204}