diff --git a/doc/syntax.md b/doc/syntax.md index 279442b56228..607bd16d2e8f 100644 --- a/doc/syntax.md +++ b/doc/syntax.md @@ -267,9 +267,10 @@ as `std::tm` have the following syntax:
 chrono_format_spec ::= [[fill]align][width]["." precision][chrono_specs]
 chrono_specs       ::= [chrono_specs] conversion_spec | chrono_specs literal_char
-conversion_spec    ::= "%" [modifier] chrono_type
+conversion_spec    ::= "%" [padding_modifier] [locale_modifier] chrono_type
 literal_char       ::= <a character other than '{', '}' or '%'>
-modifier           ::= "E" | "O"
+padding_modifier   ::= "-" | "_"  | "0"
+locale_modifier    ::= "E" | "O"
 chrono_type        ::= "a" | "A" | "b" | "B" | "c" | "C" | "d" | "D" | "e" | "F" |
                        "g" | "G" | "h" | "H" | "I" | "j" | "m" | "M" | "n" | "p" |
                        "q" | "Q" | "r" | "R" | "S" | "t" | "T" | "u" | "U" | "V" |
@@ -327,6 +328,17 @@ The available presentation types (*chrono_type*) are:
 Specifiers that have a calendaric component such as `'d'` (the day of
 month) are valid only for `std::tm` and time points but not durations.
 
+The available padding modifiers (*padding_modifier*) are:
+
+| Type  | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
+|-------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `'-'` | Pad a numeric result with spaces.                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
+| `'_'` | Do not pad a numeric result string.                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
+| `'0'` | Pad a numeric result string with zeros.                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
+
+Currently, these modifiers are only supported for the ``'H', 'I', 'M', 'S', 'U', 'V'``
+and ``'W'`` presentation types.
+
 ## Range Format Specifications
 
 Format specifications for range types have the following syntax:
diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h
index d61b083273f9..aac422d162a3 100644
--- a/include/fmt/chrono.h
+++ b/include/fmt/chrono.h
@@ -795,22 +795,22 @@ FMT_CONSTEXPR auto parse_chrono_format(const Char* begin, const Char* end,
       break;
     // Day of the year/month:
     case 'U':
-      handler.on_dec0_week_of_year(numeric_system::standard);
+      handler.on_dec0_week_of_year(numeric_system::standard, pad);
       break;
     case 'W':
-      handler.on_dec1_week_of_year(numeric_system::standard);
+      handler.on_dec1_week_of_year(numeric_system::standard, pad);
       break;
     case 'V':
-      handler.on_iso_week_of_year(numeric_system::standard);
+      handler.on_iso_week_of_year(numeric_system::standard, pad);
       break;
     case 'j':
       handler.on_day_of_year();
       break;
     case 'd':
-      handler.on_day_of_month(numeric_system::standard);
+      handler.on_day_of_month(numeric_system::standard, pad);
       break;
     case 'e':
-      handler.on_day_of_month_space(numeric_system::standard);
+      handler.on_day_of_month(numeric_system::standard, pad_type::space);
       break;
     // Hour, minute, second:
     case 'H':
@@ -907,19 +907,19 @@ FMT_CONSTEXPR auto parse_chrono_format(const Char* begin, const Char* end,
         handler.on_dec_month(numeric_system::alternative);
         break;
       case 'U':
-        handler.on_dec0_week_of_year(numeric_system::alternative);
+        handler.on_dec0_week_of_year(numeric_system::alternative, pad);
         break;
       case 'W':
-        handler.on_dec1_week_of_year(numeric_system::alternative);
+        handler.on_dec1_week_of_year(numeric_system::alternative, pad);
         break;
       case 'V':
-        handler.on_iso_week_of_year(numeric_system::alternative);
+        handler.on_iso_week_of_year(numeric_system::alternative, pad);
         break;
       case 'd':
-        handler.on_day_of_month(numeric_system::alternative);
+        handler.on_day_of_month(numeric_system::alternative, pad);
         break;
       case 'e':
-        handler.on_day_of_month_space(numeric_system::alternative);
+        handler.on_day_of_month(numeric_system::alternative, pad_type::space);
         break;
       case 'w':
         handler.on_dec0_weekday(numeric_system::alternative);
@@ -972,12 +972,19 @@ template  struct null_chrono_spec_handler {
   FMT_CONSTEXPR void on_abbr_month() { unsupported(); }
   FMT_CONSTEXPR void on_full_month() { unsupported(); }
   FMT_CONSTEXPR void on_dec_month(numeric_system) { unsupported(); }
-  FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system) { unsupported(); }
-  FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system) { unsupported(); }
-  FMT_CONSTEXPR void on_iso_week_of_year(numeric_system) { unsupported(); }
+  FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) {
+    unsupported();
+  }
+  FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) {
+    unsupported();
+  }
+  FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) {
+    unsupported();
+  }
   FMT_CONSTEXPR void on_day_of_year() { unsupported(); }
-  FMT_CONSTEXPR void on_day_of_month(numeric_system) { unsupported(); }
-  FMT_CONSTEXPR void on_day_of_month_space(numeric_system) { unsupported(); }
+  FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) {
+    unsupported();
+  }
   FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); }
   FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); }
   FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); }
@@ -1015,12 +1022,11 @@ struct tm_format_checker : null_chrono_spec_handler {
   FMT_CONSTEXPR void on_abbr_month() {}
   FMT_CONSTEXPR void on_full_month() {}
   FMT_CONSTEXPR void on_dec_month(numeric_system) {}
-  FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system) {}
-  FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system) {}
-  FMT_CONSTEXPR void on_iso_week_of_year(numeric_system) {}
+  FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) {}
+  FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) {}
+  FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) {}
   FMT_CONSTEXPR void on_day_of_year() {}
-  FMT_CONSTEXPR void on_day_of_month(numeric_system) {}
-  FMT_CONSTEXPR void on_day_of_month_space(numeric_system) {}
+  FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) {}
   FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {}
   FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {}
   FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {}
@@ -1454,7 +1460,7 @@ class tm_writer {
       *out_++ = ' ';
       on_abbr_month();
       *out_++ = ' ';
-      on_day_of_month_space(numeric_system::standard);
+      on_day_of_month(numeric_system::standard, pad_type::space);
       *out_++ = ' ';
       on_iso_time();
       *out_++ = ' ';
@@ -1541,24 +1547,26 @@ class tm_writer {
     format_localized('m', 'O');
   }
 
-  void on_dec0_week_of_year(numeric_system ns) {
+  void on_dec0_week_of_year(numeric_system ns, pad_type pad) {
     if (is_classic_ || ns == numeric_system::standard)
-      return write2((tm_yday() + days_per_week - tm_wday()) / days_per_week);
+      return write2((tm_yday() + days_per_week - tm_wday()) / days_per_week,
+                    pad);
     format_localized('U', 'O');
   }
-  void on_dec1_week_of_year(numeric_system ns) {
+  void on_dec1_week_of_year(numeric_system ns, pad_type pad) {
     if (is_classic_ || ns == numeric_system::standard) {
       auto wday = tm_wday();
       write2((tm_yday() + days_per_week -
               (wday == 0 ? (days_per_week - 1) : (wday - 1))) /
-             days_per_week);
+                 days_per_week,
+             pad);
     } else {
       format_localized('W', 'O');
     }
   }
-  void on_iso_week_of_year(numeric_system ns) {
+  void on_iso_week_of_year(numeric_system ns, pad_type pad) {
     if (is_classic_ || ns == numeric_system::standard)
-      return write2(tm_iso_week_of_year());
+      return write2(tm_iso_week_of_year(), pad);
     format_localized('V', 'O');
   }
 
@@ -1572,20 +1580,11 @@ class tm_writer {
     write1(yday / 100);
     write2(yday % 100);
   }
-  void on_day_of_month(numeric_system ns) {
-    if (is_classic_ || ns == numeric_system::standard) return write2(tm_mday());
+  void on_day_of_month(numeric_system ns, pad_type pad) {
+    if (is_classic_ || ns == numeric_system::standard)
+      return write2(tm_mday(), pad);
     format_localized('d', 'O');
   }
-  void on_day_of_month_space(numeric_system ns) {
-    if (is_classic_ || ns == numeric_system::standard) {
-      auto mday = to_unsigned(tm_mday()) % 100;
-      const char* d2 = digits2(mday);
-      *out_++ = mday < 10 ? ' ' : d2[0];
-      *out_++ = d2[1];
-    } else {
-      format_localized('e', 'O');
-    }
-  }
 
   void on_24_hour(numeric_system ns, pad_type pad) {
     if (is_classic_ || ns == numeric_system::standard)
@@ -1933,11 +1932,10 @@ struct chrono_formatter {
   void on_iso_week_based_year() {}
   void on_iso_week_based_short_year() {}
   void on_dec_month(numeric_system) {}
-  void on_dec0_week_of_year(numeric_system) {}
-  void on_dec1_week_of_year(numeric_system) {}
-  void on_iso_week_of_year(numeric_system) {}
-  void on_day_of_month(numeric_system) {}
-  void on_day_of_month_space(numeric_system) {}
+  void on_dec0_week_of_year(numeric_system, pad_type) {}
+  void on_dec1_week_of_year(numeric_system, pad_type) {}
+  void on_iso_week_of_year(numeric_system, pad_type) {}
+  void on_day_of_month(numeric_system, pad_type) {}
 
   void on_day_of_year() {
     if (handle_nan_inf()) return;
@@ -2156,7 +2154,8 @@ struct formatter : private formatter {
     if (use_tm_formatter_) return formatter::format(time, ctx);
     detail::get_locale loc(false, ctx.locale());
     auto w = detail::tm_writer(loc, ctx.out(), time);
-    w.on_day_of_month(detail::numeric_system::standard);
+    w.on_day_of_month(detail::numeric_system::standard,
+                      detail::pad_type::unspecified);
     return w.out();
   }
 };
diff --git a/test/chrono-test.cc b/test/chrono-test.cc
index 1055c7982097..0a70210d88de 100644
--- a/test/chrono-test.cc
+++ b/test/chrono-test.cc
@@ -1004,12 +1004,32 @@ TEST(chrono_test, glibc_extensions) {
   }
 
   {
-    const auto d = std::chrono::duration(3.14);
+    auto d = std::chrono::duration(3.14);
     EXPECT_EQ(fmt::format("{:%S}", d), "03.140000");
     EXPECT_EQ(fmt::format("{:%0S}", d), "03.140000");
     EXPECT_EQ(fmt::format("{:%_S}", d), " 3.140000");
     EXPECT_EQ(fmt::format("{:%-S}", d), "3.140000");
   }
+
+  {
+    auto t = std::tm();
+    t.tm_yday = 7;
+    EXPECT_EQ(fmt::format("{:%U,%W,%V}", t), "02,01,01");
+    EXPECT_EQ(fmt::format("{:%0U,%0W,%0V}", t), "02,01,01");
+    EXPECT_EQ(fmt::format("{:%_U,%_W,%_V}", t), " 2, 1, 1");
+    EXPECT_EQ(fmt::format("{:%-U,%-W,%-V}", t), "2,1,1");
+  }
+
+  {
+    auto t = std::tm();
+    t.tm_mday = 7;
+    EXPECT_EQ(fmt::format("{:%d}", t), "07");
+    EXPECT_EQ(fmt::format("{:%0d}", t), "07");
+    EXPECT_EQ(fmt::format("{:%_d}", t), " 7");
+    EXPECT_EQ(fmt::format("{:%-d}", t), "7");
+
+    EXPECT_EQ(fmt::format("{:%e}", t), " 7");
+  }
 }
 
 TEST(chrono_test, out_of_range) {