Skip to main content

yan_log/
unit.rs

1use std::fs;
2use std::io;
3use std::path::PathBuf;
4use std::time::SystemTime;
5
6/// 将 u32 数字格式化为2位数字的字节切片
7/// - 将输入的数字格式化为固定2位宽度的字符串表示,不足两位时前面补零。
8/// - 对于0-9的数字使用预定义的字节字面量以获得最佳性能。
9///
10/// # 参数
11/// - `i`: 要格式化的无符号32位整数
12/// - `buf`: 用于存储格式化结果的缓冲区,必须至少10字节长度
13///
14/// # 返回值
15/// - `&[u8]`: 指向格式化结果的字节切片引用,长度为2
16///
17/// # 性能优化
18/// - 对于0-9的数字直接返回预定义的字节字面量,避免函数调用
19/// - 对于10及以上的数字使用高效的算法
20/// - 使用内联优化减少函数调用开销
21/// # 示例
22/// ```rust,ignore
23/// let mut buf = [0u8; 3];
24/// let result = format_u8_as_padded_2_digits(5, &mut buf);
25/// assert_eq!(result, b"05");
26///
27/// let result2 = format_u8_as_padded_2_digits(15, &mut buf);
28/// assert_eq!(result2, b"15");
29/// ```
30#[inline]
31pub(crate) fn format_u8_as_padded_2_digits(i: u8, buf: &mut [u8; 3]) -> &[u8] {
32    match i {
33        0 => b"00",
34        1 => b"01",
35        2 => b"02",
36        3 => b"03",
37        4 => b"04",
38        5 => b"05",
39        6 => b"06",
40        7 => b"07",
41        8 => b"08",
42        9 => b"09",
43        _ => proc_tools_core::utils_core::impl_to_ascii::itoa_buf_u8(buf, i),
44    }
45}
46
47/// 将 u32 数字格式化为3位数字的字节切片
48/// - 将输入的数字格式化为固定3位宽度的字符串表示,不足三位时前面补零。
49/// - 针对不同范围的数字使用不同的优化策略。
50///
51/// # 参数
52/// - `i`: 要格式化的无符号32位整数
53/// - `buf`: 用于存储格式化结果的缓冲区,必须至少10字节长度
54///
55/// # 返回值
56/// - `&[u8]`: 指向格式化结果的字节切片引用,长度为3
57///
58/// # 算法说明
59/// - 0-9: 手动填充两个前导零
60/// - 10-99: 手动填充一个前导零
61/// - 100+: 使用高效的算法
62///
63/// # 性能优化
64/// - 使用内联(always)确保关键路径的性能
65/// - 对小数字进行手动处理避免函数调用
66/// - 缓冲区预初始化为'0'字符减少赋值操作
67///
68/// # 示例
69/// ```rust,ignore
70/// let mut buf = [b'0'; 5];
71/// let result = format_u16_as_padded_3_digits(5, &mut buf);
72/// assert_eq!(result, b"005");
73///
74/// let result2 = format_u16_as_padded_3_digits(42, &mut buf);
75/// assert_eq!(result2, b"042");
76///
77/// let result3 = format_u16_as_padded_3_digits(123, &mut buf);
78/// assert_eq!(result3, b"123");
79/// ```
80#[inline(always)]
81pub(crate) fn format_u16_as_padded_3_digits(i: u16, buf: &mut [u8; 5]) -> &[u8] {
82    if i < 10 {
83        buf[0] = b'0';
84        buf[1] = b'0';
85        buf[2] = b'0' + i as u8;
86        &buf[0..3]
87    } else if i < 100 {
88        buf[0] = b'0';
89        buf[1] = b'0' + (i / 10) as u8; // 十位
90        buf[2] = b'0' + (i % 10) as u8; // 个位
91        &buf[0..3]
92    } else {
93        proc_tools_core::utils_core::impl_to_ascii::itoa_buf_u16(buf, i)
94    }
95}
96
97/// 将毫秒级时间戳转换为日期时间组件
98/// - 将自 Unix 纪元(1970-01-01 00:00:00 UTC)以来的毫秒数转换为对应的
99/// - 使用 UTC/GMT (世界协调时间),如果要用北京时间的时区,需手动为时间戳加 28_800_000(8小时)
100/// - 年、月、日、时、分、秒和毫秒组件。使用简单的计算避免昂贵的日期时间库调用。
101///
102/// # 参数
103/// - `timestamp`: 毫秒级 Unix 时间戳
104///
105/// # 返回值
106/// - `(u32, u8, u8, u8, u8, u8, u16)`: 日期时间组件的元组,包含:
107///   - `u32`: 年份(如 2023)
108///   - `u8`: 月份(1-12)
109///   - `u8`: 日期(1-31)
110///   - `u8`: 小时(0-23)
111///   - `u8`: 分钟(0-59)
112///   - `u8`: 秒数(0-59)
113///   - `u16`: 毫秒数(0-999)
114///
115/// # 算法特点
116/// - 使用 400 年、100 年、4 年周期进行高效年份计算
117/// - 时间复杂度为 O(1),最多进行 3 次年份调整和 12 次月份调整
118/// - 正确处理闰年规则:能被4整除但不能被100整除,或能被400整除
119///
120/// # 注意事项
121/// - 输入时间戳应为毫秒级(Unix 时间戳 × 1000)
122/// - 返回的月份和日期从1开始(1月=1,1日=1)
123/// - 算法假设格里高利历法,适用于 1970 年之后的日期
124///
125/// # 示例
126/// ```rust,ignore
127/// let timestamp = 1698242456123; // 世界协调时间:2023-10-25 14:00:56.123
128/// let (year, month, day, hour, minute, second, millis) = timestamp_to_datetime(timestamp);
129/// assert_eq!(year, 2023);
130/// assert_eq!(month, 10);
131/// assert_eq!(day, 25);
132/// assert_eq!(hour, 14);
133/// assert_eq!(minute, 0);
134/// assert_eq!(second, 56);
135/// assert_eq!(millis, 123);
136///
137/// //北京时间需要在原时间戳上加28_800_000
138/// let mut timestamp = 1698242456123; // 世界协调时间:2023-10-25 14:00:56.123
139/// timestamp += 28_800_000; // 北京时间:2023-10-25 22:00:56.123
140/// let (year, month, day, hour, minute, second, millis) = timestamp_to_datetime(timestamp);
141/// assert_eq!(hour, 22);
142/// ```
143///
144/// # 性能
145/// - 使用整数运算,避免浮点数计算
146/// - 循环次数有上限(年份最多3次,月份最多12次)
147/// - 适合高性能场景,如日志处理、时间序列数据分析
148#[inline(always)]
149pub fn timestamp_ms_to_datetime(timestamp: u128) -> (u32, u8, u8, u8, u8, u8, u16) {
150    // 毫秒时间戳 -> 秒 + 毫秒
151    let total_seconds = timestamp / 1000;
152    let milliseconds = timestamp % 1000;
153
154    // 计算天数和当天的秒数
155    let days = total_seconds / 86400;
156    let seconds_in_day = total_seconds % 86400;
157
158    // 计算时分秒
159    let hours = (seconds_in_day / 3600) as u8;
160    let minutes = ((seconds_in_day % 3600) / 60) as u8;
161    let seconds = (seconds_in_day % 60) as u8;
162
163    // 精确年份计算(400年/100年/4年周期)
164    let n400 = days / 146097; // 400年周期天数: 146097
165    let mut year = 1970 + n400 * 400;
166    let mut remaining_days = days % 146097;
167
168    let n100 = remaining_days / 36524; // 100年周期天数: 36524
169    year += n100 * 100;
170    remaining_days %= 36524;
171
172    let n4 = remaining_days / 1461; // 4年周期天数: 1461
173    year += n4 * 4;
174    remaining_days %= 1461;
175
176    // 剩余天数最多处理3次(O(1)时间)
177    for _ in 0..3 {
178        let is_leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
179        if remaining_days >= 366 && is_leap {
180            remaining_days -= 366;
181            year += 1;
182        } else if remaining_days >= 365 {
183            remaining_days -= 365;
184            year += 1;
185        } else {
186            break;
187        }
188    }
189
190    // 计算月份和日期(最多12次循环,常数时间)
191    let mut month = 1;
192    let mut days_in_month = 31;
193    while remaining_days >= days_in_month {
194        remaining_days -= days_in_month;
195        month += 1;
196        days_in_month = match month {
197            2 => {
198                if (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) {
199                    29
200                } else {
201                    28
202                }
203            }
204            4 | 6 | 9 | 11 => 30,
205            _ => 31,
206        };
207    }
208
209    let day = (remaining_days + 1) as u8;
210
211    (
212        year as u32,
213        month,
214        day,
215        hours,
216        minutes,
217        seconds,
218        milliseconds as u16,
219    )
220}
221
222/// 根据日志文件命名规则,查询目录下所有匹配的文件路径,并按修改时间从新到旧排序
223///
224/// # 参数
225/// - `dir_path`: 日志目录路径
226/// - `pattern`: 文件命名模式(如 "app_%Y-%m-%d.log")
227///
228/// # 返回
229/// - `Ok(Vec<PathBuf>)`: 匹配文件的路径列表(按修改时间新到旧排序)
230/// - `Err(std::io::Error)`: 目录读取或文件操作错误
231#[inline]
232pub(crate) fn find_log_files(dir_path: &str, pattern: &str) -> io::Result<Vec<PathBuf>> {
233    let fragments = parse_pattern(pattern);
234    let mut files: Vec<(PathBuf, SystemTime)> = Vec::new();
235
236    for entry in fs::read_dir(dir_path)? {
237        let entry = entry?;
238        let path = entry.path();
239        if !path.is_file() || path.file_name().is_none() {
240            continue;
241        }
242
243        let file_name_lossy = path.file_name().unwrap().to_string_lossy();
244
245        if !matches_pattern_bytes(&file_name_lossy, &fragments) {
246            continue;
247        }
248
249        let mod_time = fs::metadata(&path)?.modified()?;
250        files.push((path, mod_time));
251    }
252
253    files.sort_by(|a, b| b.1.cmp(&a.1));
254    Ok(files.into_iter().map(|(path, _)| path).collect())
255}
256
257/// 解析日志文件名模式字符串
258/// - 将模式字符串解析为片段序列,用于后续的文件名匹配和生成。
259/// - 支持占位符语法:`%Y`(四位数年份), `%m`(月份), `%d`(日期), `%H`(小时), `%M`(分钟), `%S`(秒), `%i`(任意数字)
260///
261/// # 参数
262/// - `pattern`: 模式字符串,可以包含固定文本和占位符
263///
264/// # 返回值
265/// - `Vec<Fragment>`: 解析后的片段序列,用于文件名模式匹配
266///
267/// # 处理流程
268/// 1. 逐字节扫描模式字符串
269/// 2. 遇到 `%` 字符时检查后续字符是否为有效占位符
270/// 3. 将固定文本和占位符分别存储为不同的片段类型
271/// 4. 支持UTF-8字符的安全处理
272///
273/// # 示例
274/// ```
275/// use log_processor::parse_pattern;
276///
277/// let fragments = parse_pattern("%Y-%m-%d %H:%M:%S-%i.log");
278/// // 解析结果包含固定文本和日期时间占位符
279/// ```
280///
281/// # 注意事项
282/// - 不支持嵌套或复杂的占位符语法
283/// - 未识别的占位符会被忽略
284/// - 模式字符串必须是有效的UTF-8编码
285#[inline(always)]
286fn parse_pattern(pattern: &str) -> Vec<Fragment> {
287    let mut fragments: Vec<Fragment> = Vec::new();
288    let mut i: usize = 0;
289    let mut poi: usize = 0;
290    let bytes = pattern.as_bytes();
291    while i < pattern.len() {
292        if bytes[i] == b'%' {
293            if i + 1 == pattern.len() {
294                break;
295            }
296            let next_byte = bytes[i + 1];
297            let char_len = utf8_char_len(next_byte);
298            if char_len == 1 {
299                fragments.push(Fragment::Fixed(Box::from(&pattern[poi..i])));
300                if next_byte == b'Y' {
301                    fragments.push(Fragment::PlaceholderFourDigits);
302                } else if next_byte == b'm'
303                    || next_byte == b'd'
304                    || next_byte == b'H'
305                    || next_byte == b'M'
306                    || next_byte == b'S'
307                {
308                    fragments.push(Fragment::PlaceholderTwoDigits);
309                } else if next_byte == b'i' {
310                    fragments.push(Fragment::PlaceholderAnyDigits);
311                } else {
312                    i += 1;
313                    fragments.pop();
314                    continue;
315                }
316                i += 2;
317                poi = i;
318            } else {
319                // UTF-8字符处理,确保不会越界
320                let actual_len = char_len.min(bytes.len() - i);
321                i += actual_len;
322            }
323        } else {
324            i += 1;
325        }
326    }
327    if poi < pattern.len() {
328        fragments.push(Fragment::Fixed(Box::from(&pattern[poi..pattern.len()])));
329    }
330    fragments
331}
332
333/// 文件名模式片段枚举
334enum Fragment {
335    // 四位数数字占位符
336    PlaceholderFourDigits,
337    // 两位数数字占位符
338    PlaceholderTwoDigits,
339    // 任意位数数字占位符
340    PlaceholderAnyDigits,
341    // 固定字符串
342    Fixed(Box<str>),
343}
344
345/// 计算 UTF-8 字符的字节长度
346///
347/// # 参数
348/// - `next_byte`: UTF-8字符的首字节
349///
350/// # 返回值
351/// - `usize`: UTF-8字符的字节长度 (1-4)
352///
353/// # UTF-8编码规则
354/// - 单字节: 0xxxxxxx
355/// - 双字节: 110xxxxx
356/// - 三字节: 1110xxxx
357/// - 四字节: 11110xxx
358///
359/// # 示例
360/// ```rust`ignore
361/// assert_eq!(utf8_char_len(b'a'), 1);    // ASCII字符
362/// assert_eq!(utf8_char_len(0xC3), 2);    // 双字节UTF-8
363/// assert_eq!(utf8_char_len(0xE2), 3);    // 三字节UTF-8
364/// ```
365#[inline(always)]
366fn utf8_char_len(next_byte: u8) -> usize {
367    if next_byte & 0b1110_0000 == 0b1100_0000 {
368        2
369    } else if next_byte & 0b1111_0000 == 0b1110_0000 {
370        3
371    } else if next_byte & 0b1111_1000 == 0b1111_0000 {
372        4
373    } else {
374        1 // 单字节ASCII字符或无效UTF-8
375    }
376}
377
378/// 检查文件名是否匹配片段模式
379/// - 验证给定的文件名是否符合预解析的片段模式结构。
380/// - 逐个匹配片段,确保文件名在结构和内容上符合预期格式。
381///
382/// # 参数
383/// - `file_name`: 要检查的文件名字符串
384/// - `fragments`: 预解析的模式片段序列
385///
386/// # 返回值
387/// - `bool`: 文件名是否匹配模式
388///   - `true`: 文件名完全匹配所有片段
389///   - `false`: 文件名不符合模式要求
390///
391/// # 匹配规则
392/// - `Fixed`: 精确匹配固定字符串
393/// - `PlaceholderTwoDigits`: 匹配连续两个 `ASCII` 数字
394/// - `PlaceholderFourDigits`: 匹配连续四个 `ASCII` 数字
395/// - `PlaceholderAnyDigits`: 匹配连续 1-39 个 `ASCII` 数字
396#[inline(always)]
397fn matches_pattern_bytes(file_name: &str, fragments: &[Fragment]) -> bool {
398    let mut pos = 0;
399    let file_name_bytes = file_name.as_bytes();
400    for fragment in fragments {
401        match fragment {
402            Fragment::Fixed(s) => {
403                if !file_name_bytes[pos..pos + s.len()].starts_with(s.as_bytes()) {
404                    return false;
405                }
406                pos += s.len();
407            }
408            Fragment::PlaceholderTwoDigits => {
409                if !file_name_bytes[pos].is_ascii_digit() {
410                    return false;
411                }
412                pos += 1;
413                if !file_name_bytes[pos].is_ascii_digit() {
414                    return false;
415                }
416                pos += 1;
417            }
418            Fragment::PlaceholderFourDigits => {
419                for ch in &file_name_bytes[pos..pos + 4] {
420                    if !ch.is_ascii_digit() {
421                        return false;
422                    }
423                }
424                pos += 4;
425            }
426            Fragment::PlaceholderAnyDigits => {
427                let remain_str = &file_name_bytes[pos..];
428                let remain_len = remain_str.len().min(39);
429                let mut any_digits = 0;
430                for i in &file_name_bytes[pos..pos + remain_len] {
431                    if !i.is_ascii_digit() {
432                        break;
433                    }
434                    any_digits += 1;
435                }
436                if any_digits == 0 {
437                    return false;
438                }
439                pos += any_digits;
440            }
441        }
442    }
443    pos == file_name_bytes.len()
444}