Hacker News new | ask | show | jobs
by AtroxDev 1630 days ago
I agree. I really like how the `time` crate[0] in the rust world handles this[1].

with your example:

> format_description!("[year]/[month]/[day]")

0: https://crates.io/crates/time

1: https://time-rs.github.io/book/api/format-description.html

2 comments

This is easier to read but not descriptive enough since there are different kinds year, month, day. Year can be 2 digits, 4 digits, regular or week year. Month can have leading zero or not, long name, short, name. Day could be Julian, leading zero or not, day of the week, etc
That's when you can be more precise if you want: `[year padding:zero repr:full base:calendar sign:automatic]`. The format is also checked on compile time.
How long until that becomes Turing-complete ;)
+1 for strongly typed and compile-time checked, but I'm still not a fan of a sub-language within a string literal. The language already has syntax to structure things, why make a separate language within strings?
In the context of the original article addressing Java 8, in the java.time.format package, there's a DateTimeFormatterBuilder that removes the sub-language element of the issue. With those builder methods, you can construct the fields in the order you want, with whatever precision, with padding, etc.
> The language already has syntax to structure things

I don’t think the language has anything suitable for these purposes. What did you have in mind?

I don't know about Rust. Several people responded that Java already has a builder interface for creating a format string. That is possibly more type erased than the Rust macro equivalent. In C++ I would use variadic templates, as I originally proposed in my root comment: `format(year(), '/', ...)`.
The format_description! macro uses the same syntax as the format_description::parse method. If you want to support user-provided formatting strings (which you do), then you need such a method already, and once you’ve got that, why do the macro differently?

That components have parameters makes the non-literal approach even less compelling: take this which can produce the likes of “2:34:56pm”:

  format_description!("[hour padding:none repr:12]:[minute]:[second][period case:lower]")
For reference, that is equivalent to this:

  use time::format_description::{FormatItem, component::Component, modifier::{Hour, Minute, Second, Period, Padding}};

  [
      FormatItem::Component(Component::Hour(Hour { padding: Padding::None, is_12_hour_clock: false })),
      FormatItem::Literal(b":"),
      FormatItem::Component(Component::Minute(Minute { padding: Padding::Zero })),
      FormatItem::Literal(b":"),
      FormatItem::Component(Component::Second(Second { padding: Padding::Zero })),
      FormatItem::Component(Component::Period(Period { is_uppercase: false, case_sensitive: true })),
  ]
You could easily provide a prettier DSL so that you could write something like this:

  use time::format_description::shorthand::{HOUR, MINUTE, SECOND, PERIOD, literal};
  [
      HOUR.padding_none().repr_12(),
      literal(":"),
      MINUTE,
      ":".into(),  // even this if you wanted
      SECOND,
      PERIOD.case_lower(),
  ]
This wouldn’t be awful in the absence of the parse method, but really, once you have that, the format_description macro is just what you want: compact, checked at compile time, and matching a runtime equivalent which can take user-provided format strings.

(Now there are two or three changes I’d prefer to make to format_description’s syntax: I’d use = instead of :, the two being generally very similar but : far more regularly occurring in literal parts, so that the different = would make it scan better; and I think that escaping opening square brackets by doubling them but not requiring doubling for closing square brackets was a particularly bad idea; and I’m mildly inclined to prefer {} to []. So I might end up with "{hour padding=none repr=12}:{minute}:{second}{period case=lower}".)