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 {
113        0
114    } else {
115        value
116    }
117}
118
119/// Formats a time value as `HH:MM:SS[.fraction]`.
120fn format_time(time: &MysqlTime, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121    if time.neg {
122        f.write_char('-')?;
123    }
124
125    write!(
126        f,
127        "{:02}:{:02}:{:02}",
128        time.hour,
129        trim_two_digits(time.minute),
130        trim_two_digits(time.second),
131    )?;
132    format_useconds(time.second_part, f)?;
133    Ok(())
134}
135
136/// Formats a datetime value with an optional fractional part (if formatter precision is given).
137fn format_datetime(time: &MysqlTime, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138    format_date_and_time(time, f)?;
139    format_useconds(time.second_part, f)?;
140    if time.time_type == MysqlTimestampType::MYSQL_TIMESTAMP_DATETIME_TZ {
141        format_tz(time.time_zone_displacement, f)?;
142    }
143    Ok(())
144}
145
146/// Formats date and time part as 'YYYY-MM-DD hh:mm:ss'
147fn format_date_and_time(time: &MysqlTime, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148    write!(
149        f,
150        "{:02}{:02}-{:02}-{:02} {:02}:{:02}:{:02}",
151        trim_two_digits(time.year / 100),
152        trim_two_digits(time.year % 100),
153        trim_two_digits(time.month),
154        trim_two_digits(time.day),
155        trim_two_digits(time.hour),
156        trim_two_digits(time.minute),
157        trim_two_digits(time.second),
158    )
159}
160
161/// Formats a date value as 'YYYY-MM-DD'.
162fn format_date(time: &MysqlTime, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163    write!(
164        f,
165        "{:02}{:02}-{:02}-{:02}",
166        trim_two_digits(time.year / 100),
167        trim_two_digits(time.year % 100),
168        trim_two_digits(time.month),
169        trim_two_digits(time.day),
170    )
171}
172
173/// Only formats useconds if formatter precision is given (will be truncated to 6)
174fn format_useconds(mut useconds: u32, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175    let Some(dec) = f.precision().map(|x| min(x, 6)) else {
176        return Ok(());
177    };
178
179    if dec == 0 {
180        return Ok(());
181    }
182
183    useconds %= 1_000_000;
184
185    for _ in 0..(6 - dec) {
186        useconds /= 10;
187    }
188
189    write!(f, ".{:0width$}", useconds, width = dec)
190}
191
192fn format_tz(tzd: i32, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193    write!(f, "+{:02}:{:02}", tzd / 3600, tzd.abs() / 60 % 60)
194}
195
196#[derive(Debug, Clone, PartialEq)]
197#[repr(C)]
198#[allow(non_camel_case_types)]
199pub enum MysqlTimestampType {
200    /// Textual representation of this value is an empty string
201    MYSQL_TIMESTAMP_NONE = -2,
202    /// Textual representation of this value is an empty string
203    MYSQL_TIMESTAMP_ERROR = -1,
204    MYSQL_TIMESTAMP_DATE = 0,
205    MYSQL_TIMESTAMP_DATETIME = 1,
206    MYSQL_TIMESTAMP_TIME = 2,
207    MYSQL_TIMESTAMP_DATETIME_TZ = 3,
208}