pub fn redshift_to_strftime(fmt: &str) -> String {
let mut out = String::with_capacity(fmt.len());
let chars: Vec<char> = fmt.chars().collect();
let len = chars.len();
let mut i = 0;
while i < len {
if chars[i] == '"' {
i += 1;
while i < len && chars[i] != '"' {
out.push(chars[i]);
i += 1;
}
if i < len {
i += 1; }
continue;
}
if let Some((replacement, consumed)) = match_redshift_token(&chars, i, len) {
out.push_str(replacement);
i += consumed;
} else {
out.push(chars[i]);
i += 1;
}
}
out
}
fn match_redshift_token(chars: &[char], i: usize, len: usize) -> Option<(&'static str, usize)> {
let remaining = len - i;
if remaining >= 6 {
let t6: String = chars[i..i + 6].iter().collect();
let t6_upper = t6.to_uppercase();
if t6_upper.as_str() == "SSSSSS" {
return Some(("%f", 6)); }
}
if remaining >= 5 {
let t5: String = chars[i..i + 5].iter().collect();
let t5_upper = t5.to_uppercase();
match t5_upper.as_str() {
"MONTH" => return Some(("%B", 5)), "IYYY" | "IYYYY" => {} _ => {}
}
}
if remaining >= 4 {
let t4: String = chars[i..i + 4].iter().collect();
let t4_upper = t4.to_uppercase();
match t4_upper.as_str() {
"YYYY" => return Some(("%Y", 4)),
"HH24" => return Some(("%H", 4)),
"HH12" => return Some(("%I", 4)),
"IYYY" => return Some(("%G", 4)), "SSSS" => return Some(("%f", 4)), _ => {}
}
}
if remaining >= 3 {
let t3: String = chars[i..i + 3].iter().collect();
let t3_upper = t3.to_uppercase();
match t3_upper.as_str() {
"MON" => return Some(("%b", 3)), "DAY" => return Some(("%A", 3)), "DDD" => return Some(("%j", 3)), _ if t3_upper == "DY " || (remaining == 3 && t3_upper.starts_with("DY")) => {}
_ => {}
}
}
if remaining >= 2 {
let t2: String = chars[i..i + 2].iter().collect();
let t2_upper = t2.to_uppercase();
match t2_upper.as_str() {
"YY" => return Some(("%y", 2)),
"MM" => return Some(("%m", 2)),
"DD" => return Some(("%d", 2)),
"HH" => return Some(("%H", 2)),
"MI" => return Some(("%M", 2)),
"SS" => return Some(("%S", 2)),
"MS" => return Some(("%g", 2)), "US" => return Some(("%f", 2)), "AM" | "PM" => return Some(("%p", 2)),
"TZ" => return Some(("%Z", 2)),
"OF" => return Some(("%z", 2)), "DY" => return Some(("%a", 2)), "WW" => return Some(("%W", 2)), "IW" => return Some(("%V", 2)), "ID" => return Some(("%u", 2)), "CC" => return Some(("%C", 2)), _ if t2_upper.starts_with('Q')
&& !t2_upper.chars().nth(1).unwrap_or(' ').is_alphabetic() => {}
_ => {}
}
}
let ch_upper = chars[i].to_uppercase().next().unwrap_or(chars[i]);
match ch_upper {
'D' if remaining >= 1 && (remaining < 2 || !chars[i + 1].is_alphabetic()) => {
return Some(("%w", 1)); }
'J' if remaining >= 1 && (remaining < 2 || !chars[i + 1].is_alphabetic()) => {
return Some(("%j", 1)); }
_ => {}
}
None
}
pub fn trino_to_strftime(fmt: &str) -> String {
let mut out = String::with_capacity(fmt.len());
let chars: Vec<char> = fmt.chars().collect();
let len = chars.len();
let mut i = 0;
while i < len {
if chars[i] == '\'' {
i += 1;
if i < len && chars[i] == '\'' {
out.push('\'');
i += 1;
continue;
}
while i < len && chars[i] != '\'' {
out.push(chars[i]);
i += 1;
}
if i < len {
i += 1; }
continue;
}
let ch = chars[i];
if ch.is_ascii_alphabetic() {
let mut run = 1;
while i + run < len && chars[i + run] == ch {
run += 1;
}
let replacement = match_java_token(ch, run);
out.push_str(replacement);
i += run;
} else {
out.push(ch);
i += 1;
}
}
out
}
fn match_java_token(ch: char, count: usize) -> &'static str {
match (ch, count) {
('G', _) => "",
('y', 1..=2) => "%y", ('y', _) => "%Y", ('Y', 1..=2) => "%y", ('Y', _) => "%G", ('u', _) => "%Y",
('M', 1..=2) => "%m", ('M', 3) => "%b", ('M', _) => "%B", ('L', 1..=2) => "%m", ('L', 3) => "%b",
('L', _) => "%B",
('d', _) => "%d", ('D', _) => "%j",
('E', 1..=3) => "%a", ('E', _) => "%A", ('e', _) => "%w",
('a', _) => "%p",
('H', _) => "%H", ('k', _) => "%H", ('h', _) => "%I", ('K', _) => "%I",
('m', _) => "%M",
('s', _) => "%S",
('S', 1..=3) => "%g", ('S', _) => "%f", ('n', _) => "%f",
('z', _) => "%Z", ('Z', _) => "%z", ('X', _) => "%z", ('x', _) => "%z", ('O', _) => "%z",
('w', _) => "%V",
('%', _) => "%%",
_ => "",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn redshift_basic_date() {
assert_eq!(redshift_to_strftime("YYYY-MM-DD"), "%Y-%m-%d");
}
#[test]
fn redshift_datetime() {
assert_eq!(
redshift_to_strftime("YYYY-MM-DD HH24:MI:SS"),
"%Y-%m-%d %H:%M:%S"
);
}
#[test]
fn redshift_12hour() {
assert_eq!(redshift_to_strftime("HH12:MI:SS AM"), "%I:%M:%S %p");
}
#[test]
fn redshift_month_name() {
assert_eq!(redshift_to_strftime("DD MON YYYY"), "%d %b %Y");
}
#[test]
fn redshift_iso_week() {
assert_eq!(redshift_to_strftime("IYYY-IW"), "%G-%V");
}
#[test]
fn redshift_microseconds() {
assert_eq!(
redshift_to_strftime("YYYY-MM-DD HH24:MI:SS.US"),
"%Y-%m-%d %H:%M:%S.%f"
);
}
#[test]
fn trino_basic_date() {
assert_eq!(trino_to_strftime("yyyy-MM-dd"), "%Y-%m-%d");
}
#[test]
fn trino_datetime() {
assert_eq!(
trino_to_strftime("yyyy-MM-dd HH:mm:ss"),
"%Y-%m-%d %H:%M:%S"
);
}
#[test]
fn trino_12hour_ampm() {
assert_eq!(trino_to_strftime("hh:mm:ss a"), "%I:%M:%S %p");
}
#[test]
fn trino_month_name() {
assert_eq!(trino_to_strftime("dd MMM yyyy"), "%d %b %Y");
}
#[test]
fn trino_full_month() {
assert_eq!(trino_to_strftime("dd MMMM yyyy"), "%d %B %Y");
}
#[test]
fn trino_quoted_literal() {
assert_eq!(trino_to_strftime("yyyy'T'HH:mm:ss"), "%YT%H:%M:%S");
}
#[test]
fn trino_milliseconds() {
assert_eq!(
trino_to_strftime("yyyy-MM-dd HH:mm:ss.SSS"),
"%Y-%m-%d %H:%M:%S.%g"
);
}
#[test]
fn trino_two_digit_year() {
assert_eq!(trino_to_strftime("yy-MM-dd"), "%y-%m-%d");
}
#[test]
fn trino_iso_week_year() {
assert_eq!(trino_to_strftime("YYYY-ww"), "%G-%V");
}
}