Compiles a new `DateTimeFormat` from the input string `s`.
(s: &str)
| 855 | impl DateTimeFormat { |
| 856 | /// Compiles a new `DateTimeFormat` from the input string `s`. |
| 857 | pub fn compile(s: &str) -> DateTimeFormat { |
| 858 | // The approach here uses the Aho-Corasick string searching algorithm to |
| 859 | // repeatedly and efficiently find the next token of interest. Tokens of |
| 860 | // interest are typically field specifiers, like "DDDD", or field |
| 861 | // modifiers, like "FM". Characters in between tokens of interest are |
| 862 | // recorded as literals. We also consider a double quote a token of |
| 863 | // interest, as a double quote disables matching of field |
| 864 | // specifiers/modifiers until the next double quote. |
| 865 | |
| 866 | struct Match { |
| 867 | start: usize, |
| 868 | end: usize, |
| 869 | token: DateTimeToken, |
| 870 | } |
| 871 | |
| 872 | let matcher = AhoCorasickBuilder::new() |
| 873 | .match_kind(aho_corasick::MatchKind::LeftmostLongest) |
| 874 | .build(DateTimeToken::patterns()) |
| 875 | .unwrap_or_else(|e| panic!("automaton build error: {e}")); |
| 876 | |
| 877 | let matches: Vec<_> = matcher |
| 878 | .find_iter(&s) |
| 879 | .map(|m| Match { |
| 880 | start: m.start(), |
| 881 | end: m.end(), |
| 882 | token: DateTimeToken::try_from( |
| 883 | u8::try_from(m.pattern().as_u32()).expect("match index fits in a u8"), |
| 884 | ) |
| 885 | .expect("match pattern missing"), |
| 886 | }) |
| 887 | .collect(); |
| 888 | |
| 889 | let mut out = Vec::new(); |
| 890 | let mut pos = 0; |
| 891 | let mut in_quotes = false; |
| 892 | for i in 0..matches.len() { |
| 893 | // Any characters since the last match are to be taken literally. |
| 894 | for c in s[pos..matches[i].start].chars() { |
| 895 | if !(in_quotes && c == '\\') { |
| 896 | // Backslash is an escape character inside of quotes. |
| 897 | out.push(DateTimeFormatNode::Literal(c)); |
| 898 | } |
| 899 | } |
| 900 | |
| 901 | if in_quotes { |
| 902 | // If we see a format specifier inside of a quoted block, it |
| 903 | // is taken literally. |
| 904 | for c in matches[i].token.as_literal().chars() { |
| 905 | out.push(DateTimeFormatNode::Literal(c)) |
| 906 | } |
| 907 | } else if let Some(field) = matches[i].token.field() { |
| 908 | // We found a format specifier. Look backwards for a fill mode |
| 909 | // toggle (fill mode is on by default), and forwards for an |
| 910 | // ordinal suffix specifier (default is no ordinal suffix). |
| 911 | let fill = i == 0 |
| 912 | || matches[i - 1].end != matches[i].start |
| 913 | || !matches[i - 1].token.is_fill_mode_toggle(); |
| 914 | let ordinal = match matches.get(i + 1) { |