Skip to content

Commit

Permalink
strftime implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
jhpratt committed Dec 3, 2024
1 parent 98569ff commit 6b43b44
Show file tree
Hide file tree
Showing 7 changed files with 588 additions and 21 deletions.
73 changes: 73 additions & 0 deletions tests/parse_format_description.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use rstest_reuse::{apply, template};
use time::error::InvalidFormatDescription;
use time::format_description::modifier::*;
use time::format_description::{self, BorrowedFormatItem, Component, OwnedFormatItem};
use time::macros::format_description;

/// Identical to `modifier!`, but obtains the value from `M<T>` automagically.
macro_rules! modifier_m {
Expand Down Expand Up @@ -798,3 +799,75 @@ fn rfc_3339() {
])
);
}

#[rstest]
#[case("foo", format_description!("foo"))]
#[case("%a", format_description!("[weekday repr:short]"))]
#[case("%A", format_description!("[weekday]"))]
#[case("%b", format_description!("[month repr:short]"))]
#[case("%B", format_description!("[month repr:long]"))]
#[case("%C", format_description!("[year repr:century]"))]
#[case("%d", format_description!("[day]"))]
#[case("%e", format_description!("[day padding:space]"))]
#[case("%g", format_description!("[year repr:last_two base:iso_week]"))]
#[case("%G", format_description!("[year base:iso_week]"))]
#[case("%h", format_description!("[month repr:short]"))]
#[case("%H", format_description!("[hour]"))]
#[case("%I", format_description!("[hour repr:12]"))]
#[case("%j", format_description!("[ordinal]"))]
#[case("%k", format_description!("[hour padding:space]"))]
#[case("%l", format_description!("[hour repr:12 padding:space]"))]
#[case("%m", format_description!("[month]"))]
#[case("%M", format_description!("[minute]"))]
#[case("%n", format_description!("\n"))]
#[case("%p", format_description!("[period]"))]
#[case("%P", format_description!("[period case:lower]"))]
#[case("%s", format_description!("[unix_timestamp]"))]
#[case("%S", format_description!("[second]"))]
#[case("%t", format_description!("\t"))]
#[case("%u", format_description!("[weekday repr:monday]"))]
#[case("%U", format_description!("[week_number repr:sunday]"))]
#[case("%V", format_description!("[week_number]"))]
#[case("%w", format_description!("[weekday repr:sunday]"))]
#[case("%W", format_description!("[week_number repr:monday]"))]
#[case("%y", format_description!("[year repr:last_two]"))]
#[case("%Y", format_description!("[year]"))]
#[case("%%", format_description!("%"))]
fn strftime_equivalence(
#[case] strftime: &str,
#[case] custom: &[BorrowedFormatItem<'_>],
) -> time::Result<()> {
let borrowed = format_description::parse_strftime_borrowed(strftime)?;
let owned = format_description::parse_strftime_owned(strftime)?;

assert_eq!(borrowed, custom);
assert_eq!(owned, OwnedFormatItem::from(custom));

Ok(())
}

#[rstest]
#[case(
"%c",
"[weekday repr:short] [month repr:short] [day padding:space] [hour]:[minute]:[second] [year]"
)]
#[case("%D", "[month]/[day]/[year repr:last_two]")]
#[case("%F", "[year]-[month repr:numerical]-[day]")]
#[case("%r", "[hour repr:12]:[minute]:[second] [period]")]
#[case("%R", "[hour]:[minute]")]
#[case("%T", "[hour]:[minute]:[second]")]
#[case("%x", "[month]/[day]/[year repr:last_two]")]
#[case("%X", "[hour]:[minute]:[second]")]
#[case("%z", "[offset_hour sign:mandatory][offset_minute]")]
fn strftime_compound_equivalence(#[case] strftime: &str, #[case] custom: &str) -> time::Result<()> {
let borrowed = format_description::parse_strftime_borrowed(strftime)?;
let owned = format_description::parse_strftime_owned(strftime)?;
let custom = format_description::parse(custom)?;
// Until equality is implemented better, we need to convert to a compound.
let custom = vec![BorrowedFormatItem::Compound(&custom)];

assert_eq!(borrowed, custom);
assert_eq!(owned, OwnedFormatItem::from(custom));

Ok(())
}
12 changes: 8 additions & 4 deletions time/src/error/invalid_format_description.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,14 @@ impl fmt::Display for InvalidFormatDescription {
context,
index,
} => {
write!(
f,
"{what} is not supported in {context} at byte index {index}"
)
if context.is_empty() {
write!(f, "{what} is not supported at byte index {index}")
} else {
write!(
f,
"{what} is not supported in {context} at byte index {index}"
)
}
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion time/src/format_description/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ pub use owned_format_item::OwnedFormatItem;

pub use self::component::Component;
#[cfg(feature = "alloc")]
pub use self::parse::{parse, parse_borrowed, parse_owned};
pub use self::parse::{
parse, parse_borrowed, parse_owned, parse_strftime_borrowed, parse_strftime_owned,
};

/// Well-known formats, typically standards.
pub mod well_known {
Expand Down
4 changes: 2 additions & 2 deletions time/src/format_description/parse/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ fn parse_component<
let Some(name) = tokens.next_if_not_whitespace() else {
let span = match leading_whitespace {
Some(Spanned { value: _, span }) => span,
None => opening_bracket.to(opening_bracket),
None => opening_bracket.to_self(),
};
return Err(Error {
_inner: unused(span.error("expected component name")),
Expand Down Expand Up @@ -277,7 +277,7 @@ fn parse_component<
return Err(Error {
_inner: unused(
location
.to(location)
.to_self()
.error("modifier must be of the form `key:value`"),
),
public: crate::error::InvalidFormatDescription::InvalidModifier {
Expand Down
15 changes: 1 addition & 14 deletions time/src/format_description/parse/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use core::iter;

use super::{unused, Error, Location, Spanned, SpannedValue};
use super::{attach_location, unused, Error, Location, Spanned, SpannedValue};

/// An iterator over the lexed tokens.
pub(super) struct Lexed<I: Iterator> {
Expand Down Expand Up @@ -130,19 +130,6 @@ pub(super) enum ComponentKind {
NotWhitespace,
}

/// Attach [`Location`] information to each byte in the iterator.
fn attach_location<'item>(
iter: impl Iterator<Item = &'item u8>,
) -> impl Iterator<Item = (&'item u8, Location)> {
let mut byte_pos = 0;

iter.map(move |byte| {
let location = Location { byte: byte_pos };
byte_pos += 1;
(byte, location)
})
}

/// Parse the string into a series of [`Token`]s.
///
/// `VERSION` controls the version of the format description that is being parsed. Currently, this
Expand Down
23 changes: 23 additions & 0 deletions time/src/format_description/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use alloc::boxed::Box;
use alloc::vec::Vec;

pub use self::strftime::{parse_strftime_borrowed, parse_strftime_owned};
use crate::{error, format_description};

/// A helper macro to make version restrictions simpler to read and write.
Expand All @@ -23,6 +24,7 @@ macro_rules! validate_version {
mod ast;
mod format_item;
mod lexer;
mod strftime;

/// A struct that is used to ensure that the version is valid.
struct Version<const N: usize>;
Expand Down Expand Up @@ -84,6 +86,19 @@ pub fn parse_owned<const VERSION: usize>(
Ok(items.into())
}

/// Attach [`Location`] information to each byte in the iterator.
fn attach_location<'item>(
iter: impl Iterator<Item = &'item u8>,
) -> impl Iterator<Item = (&'item u8, Location)> {
let mut byte_pos = 0;

iter.map(move |byte| {
let location = Location { byte: byte_pos };
byte_pos += 1;
(byte, location)
})
}

/// A location within a string.
#[derive(Clone, Copy)]
struct Location {
Expand All @@ -97,6 +112,14 @@ impl Location {
Span { start: self, end }
}

/// Create a new [`Span`] consisting entirely of `self`.
const fn to_self(self) -> Span {
Span {
start: self,
end: self,
}
}

/// Offset the location by the provided amount.
///
/// Note that this assumes the resulting location is on the same line as the original location.
Expand Down
Loading

0 comments on commit 6b43b44

Please sign in to comment.