On Thu, Jul 23, 2026 at 12:47 PM Jonathan Wakely <[email protected]> wrote:

> On Wed, 01 Jul 2026 at 14:33 +0200, Tomasz Kaminski wrote:
> >On Wed, Jul 1, 2026 at 2:15 PM Anlai Lu <[email protected]> wrote:
> >
> >> Add __detail::__chrono_write which formats a chrono object into a
> >> stack buffer via std::format_to_n with _S_empty_fs(), then writes
> >> through __ostream_insert.  A non-type template parameter _BufSize
> >> (default 128) allows per-type buffer tuning.  Each _BufSize is the
> >> smallest power of two that accommodates the longest possible formatted
> >> output for that type, including error cases.  All chrono operator<<
> >> overloads that previously used std::format, std::vformat, or
> >> basic_stringstream now use this helper.
> >>
> >> libstdc++-v3/ChangeLog:
> >>
> >>         * include/bits/chrono_io.h (__formatter_chrono::_S_empty_fs):
> >>         Make public.
> >>         (__detail::__chrono_write): New function template.
> >>         (operator<<): Use __chrono_write consistently for all chrono
> >>         types.
> >>
> >> Signed-off-by: Anlai Lu <[email protected]>
> >> ---
> >>  libstdc++-v3/include/bits/chrono_io.h | 244 ++++++--------------------
> >>  1 file changed, 52 insertions(+), 192 deletions(-)
> >>
> >LGTM. Thank you for working on this.
> >
> >>
> >> diff --git a/libstdc++-v3/include/bits/chrono_io.h
> >> b/libstdc++-v3/include/bits/chrono_io.h
> >> index f8bb485d1..d3740a0e1 100644
> >> --- a/libstdc++-v3/include/bits/chrono_io.h
> >> +++ b/libstdc++-v3/include/bits/chrono_io.h
> >> @@ -884,11 +884,13 @@ namespace __format
> >>        static constexpr const _CharT* _S_minus_empty_spec = _S_chars +
> 17;
> >>        static constexpr const _CharT* _S_empty_spec = _S_chars + 18;
> >>
> >> +    public:
> >>        [[__gnu__::__always_inline__]]
> >>        static _Dynamic_format_string<_CharT>
> >>        _S_empty_fs()
> >>        { return _Dynamic_format_string<_CharT>(_S_empty_spec); }
> >>
> >> +    protected:
> >>        static constexpr const _CharT* _S_weekdays[]
> >>        {
> >>         _GLIBCXX_WIDEN("Sunday"),
> >> @@ -3607,6 +3609,30 @@ namespace __detail
> >>         }
> >>      }
> >>
> >> +  // Format into a stack buffer via std::format_to_n with the empty
> >> +  // chrono-spec (_S_empty_fs), then write through __ostream_insert.
> >> +  // The empty spec lets each formatter use its __defSpec, producing
> >> +  // output equivalent to the corresponding operator<<.
> >> +  // _BufSize allows per-type tuning of the stack buffer size.
> >> +  template<size_t _BufSize = 128, typename _CharT, typename _Traits,
> >>
> >I am wondering about making buffer size explicit on each call, and
> >removing the default. But I will handle that before committing, if
> Jonathan
> >also like that direction.
>
> What would be the advantage of requiring it explicitly every time?
>
You immediately see what buffer size is expected for each type of chrono
object,
and if we remove the default, it will force you to think of one instead of
defaulting
to 128.

>
> >> +          typename _Arg, typename... _OptLocale>
> >> +    inline basic_ostream<_CharT, _Traits>&
> >> +    __chrono_write(basic_ostream<_CharT, _Traits>& __os,
> >> +                  const _Arg& __arg, const _OptLocale&... __loc)
> >> +    {
> >> +      static_assert(sizeof...(_OptLocale) <= 1);
> >> +      constexpr auto __fs
> >> +       = __format::__formatter_chrono<_CharT>::_S_empty_fs;
> >>
> >Nice trick for storing a function pointer. I really like it.
>
> We could just make _S_empty_spec public, and then use that instead of
> _S_empty_fs(), right?
>
Passing char const*, trigger format_string constructor from that, that will
perform
a compile time check for the format string. That will increase compile time
for no
good reason.


>
> That seems simpler to me.
>
> constexpr auto __fs = __format::__formatter_chrono<_CharT>::_S_empty_spec;
>
> and then just use __fs instead of __fs()
>
We could do that if we then use vformat, and
make_format_args/make_wformat_args,
to avoid compile-time checks.

>
> >> +      constexpr size_t __bufsize = _BufSize;
> >> +      _CharT __buf[__bufsize];
> >> +      auto __res = std::format_to_n(__buf, __bufsize, __loc...,
> >> +                                   __fs(), __arg);
> >> +      if (static_cast<size_t>(__res.size) <= __bufsize) [[likely]]
> >> +       return std::__ostream_insert(__os, __buf, __res.size);
> >> +      auto __s = std::format(__loc..., __fs(), __arg);
> >> +      return std::__ostream_insert(__os, __s.data(), __s.size());
> >> +    }
> >> +
> >>  } // namespace __detail
> >>  /// @endcond
> >>
> >> @@ -3629,14 +3655,7 @@ namespace __detail
> >>      inline basic_ostream<_CharT, _Traits>&
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os, const day& __d)
> >>      {
> >> -      using _Ctx = __format::__format_context<_CharT>;
> >> -      using _Str = basic_string_view<_CharT>;
> >> -      _Str __s = _GLIBCXX_WIDEN("{:02d} is not a valid day");
> >> -      if (__d.ok())
> >> -       __s = __s.substr(0, 6);
> >> -      auto __u = (unsigned)__d;
> >> -      __os << std::vformat(__s, make_format_args<_Ctx>(__u));
> >> -      return __os;
> >> +      return __detail::__chrono_write<32>(__os, __d);
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits,
> >> @@ -3657,18 +3676,7 @@ namespace __detail
> >>      inline basic_ostream<_CharT, _Traits>&
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os, const month& __m)
> >>      {
> >> -      using _Ctx = __format::__format_context<_CharT>;
> >> -      using _Str = basic_string_view<_CharT>;
> >> -      _Str __s = _GLIBCXX_WIDEN("{:L%b}{} is not a valid month");
> >> -      if (__m.ok())
> >> -       __os << std::vformat(__os.getloc(), __s.substr(0, 6),
> >> -                            make_format_args<_Ctx>(__m));
> >> -      else
> >> -       {
> >> -         auto __u = (unsigned)__m;
> >> -         __os << std::vformat(__s.substr(6),
> make_format_args<_Ctx>(__u));
> >> -       }
> >> -      return __os;
> >> +      return __detail::__chrono_write(__os, __m, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits,
> >> @@ -3689,18 +3697,7 @@ namespace __detail
> >>      inline basic_ostream<_CharT, _Traits>&
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os, const year& __y)
> >>      {
> >> -      using _Ctx = __format::__format_context<_CharT>;
> >> -      using _Str = basic_string_view<_CharT>;
> >> -      _Str __s = _GLIBCXX_WIDEN("-{:04d} is not a valid year");
> >> -      if (__y.ok())
> >> -       __s = __s.substr(0, 7);
> >> -      int __i = (int)__y;
> >> -      if (__i >= 0) [[likely]]
> >> -       __s.remove_prefix(1);
> >> -      else
> >> -       __i = -__i;
> >> -      __os << std::vformat(__s, make_format_args<_Ctx>(__i));
> >> -      return __os;
> >> +      return __detail::__chrono_write<32>(__os, __y);
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits,
> >> @@ -3721,18 +3718,7 @@ namespace __detail
> >>      inline basic_ostream<_CharT, _Traits>&
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os, const weekday&
> __wd)
> >>      {
> >> -      using _Ctx = __format::__format_context<_CharT>;
> >> -      using _Str = basic_string_view<_CharT>;
> >> -      _Str __s = _GLIBCXX_WIDEN("{:L%a}{} is not a valid weekday");
> >> -      if (__wd.ok())
> >> -       __os << std::vformat(__os.getloc(), __s.substr(0, 6),
> >> -                            make_format_args<_Ctx>(__wd));
> >> -      else
> >> -       {
> >> -         auto __c = __wd.c_encoding();
> >> -         __os << std::vformat(__s.substr(6),
> make_format_args<_Ctx>(__c));
> >> -       }
> >> -      return __os;
> >> +      return __detail::__chrono_write(__os, __wd, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits,
> >> @@ -3754,23 +3740,7 @@ namespace __detail
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os,
> >>                const weekday_indexed& __wdi)
> >>      {
> >> -      // The standard says to format wdi.weekday() and wdi.index()
> using
> >> -      // either "{:L}[{}]" or "{:L}[{} is not a valid index]". The {:L}
> >> spec
> >> -      // means to format the weekday using ostringstream, so just do
> that.
> >> -      basic_stringstream<_CharT> __os2;
> >> -      __os2.imbue(__os.getloc());
> >> -      __os2 << __wdi.weekday();
> >> -      const auto __i = __wdi.index();
> >> -      basic_string_view<_CharT> __s
> >> -       = _GLIBCXX_WIDEN("[ is not a valid index]");
> >> -      __os2 << __s[0];
> >> -      __os2 << std::format(_GLIBCXX_WIDEN("{}"), __i);
> >> -      if (__i >= 1 && __i <= 5)
> >> -       __os2 << __s.back();
> >> -      else
> >> -       __os2 << __s.substr(1);
> >> -      __os << __os2.view();
> >> -      return __os;
> >> +      return __detail::__chrono_write(__os, __wdi, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits>
> >> @@ -3778,29 +3748,14 @@ namespace __detail
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os,
> >>                const weekday_last& __wdl)
> >>      {
> >> -      // As above, just write straight to a stringstream, as if by
> >> "{:L}[last]"
> >> -      basic_stringstream<_CharT> __os2;
> >> -      __os2.imbue(__os.getloc());
> >> -      __os2 << __wdl.weekday() << _GLIBCXX_WIDEN("[last]");
> >> -      __os << __os2.view();
> >> -      return __os;
> >> +      return __detail::__chrono_write(__os, __wdl, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits>
> >>      inline basic_ostream<_CharT, _Traits>&
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os, const month_day&
> >> __md)
> >>      {
> >> -      // As above, just write straight to a stringstream, as if by
> >> "{:L}/{}"
> >> -      basic_stringstream<_CharT> __os2;
> >> -      __os2.imbue(__os.getloc());
> >> -      __os2 << __md.month();
> >> -      if constexpr (is_same_v<_CharT, char>)
> >> -       __os2 << '/';
> >> -      else
> >> -       __os2 << L'/';
> >> -      __os2 << __md.day();
> >> -      __os << __os2.view();
> >> -      return __os;
> >> +      return __detail::__chrono_write(__os, __md, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits,
> >> @@ -3824,12 +3779,7 @@ namespace __detail
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os,
> >>                const month_day_last& __mdl)
> >>      {
> >> -      // As above, just write straight to a stringstream, as if by
> >> "{:L}/last"
> >> -      basic_stringstream<_CharT> __os2;
> >> -      __os2.imbue(__os.getloc());
> >> -      __os2 << __mdl.month() << _GLIBCXX_WIDEN("/last");
> >> -      __os << __os2.view();
> >> -      return __os;
> >> +      return __detail::__chrono_write(__os, __mdl, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits>
> >> @@ -3837,17 +3787,7 @@ namespace __detail
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os,
> >>                const month_weekday& __mwd)
> >>      {
> >> -      // As above, just write straight to a stringstream, as if by
> >> "{:L}/{:L}"
> >> -      basic_stringstream<_CharT> __os2;
> >> -      __os2.imbue(__os.getloc());
> >> -      __os2 << __mwd.month();
> >> -      if constexpr (is_same_v<_CharT, char>)
> >> -       __os2 << '/';
> >> -      else
> >> -       __os2 << L'/';
> >> -      __os2 << __mwd.weekday_indexed();
> >> -      __os << __os2.view();
> >> -      return __os;
> >> +      return __detail::__chrono_write(__os, __mwd, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits>
> >> @@ -3855,34 +3795,14 @@ namespace __detail
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os,
> >>                const month_weekday_last& __mwdl)
> >>      {
> >> -      // As above, just write straight to a stringstream, as if by
> >> "{:L}/{:L}"
> >> -      basic_stringstream<_CharT> __os2;
> >> -      __os2.imbue(__os.getloc());
> >> -      __os2 << __mwdl.month();
> >> -      if constexpr (is_same_v<_CharT, char>)
> >> -       __os2 << '/';
> >> -      else
> >> -       __os2 << L'/';
> >> -      __os2 << __mwdl.weekday_last();
> >> -      __os << __os2.view();
> >> -      return __os;
> >> +      return __detail::__chrono_write(__os, __mwdl, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits>
> >>      inline basic_ostream<_CharT, _Traits>&
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os, const year_month&
> >> __ym)
> >>      {
> >> -      // As above, just write straight to a stringstream, as if by
> >> "{}/{:L}"
> >> -      basic_stringstream<_CharT> __os2;
> >> -      __os2.imbue(__os.getloc());
> >> -      __os2 << __ym.year();
> >> -      if constexpr (is_same_v<_CharT, char>)
> >> -       __os2 << '/';
> >> -      else
> >> -       __os2 << L'/';
> >> -      __os2 << __ym.month();
> >> -      __os << __os2.view();
> >> -      return __os;
> >> +      return __detail::__chrono_write(__os, __ym, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits,
> >> @@ -3906,12 +3826,7 @@ namespace __detail
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os,
> >>                const year_month_day& __ymd)
> >>      {
> >> -      using _Ctx = __format::__format_context<_CharT>;
> >> -      using _Str = basic_string_view<_CharT>;
> >> -      _Str __s = _GLIBCXX_WIDEN("{:%F} is not a valid date");
> >> -      __os << std::vformat(__ymd.ok() ? __s.substr(0, 5) : __s,
> >> -                          make_format_args<_Ctx>(__ymd));
> >> -      return __os;
> >> +      return __detail::__chrono_write<64>(__os, __ymd);
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits,
> >> @@ -3936,17 +3851,7 @@ namespace __detail
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os,
> >>                const year_month_day_last& __ymdl)
> >>      {
> >> -      // As above, just write straight to a stringstream, as if by
> >> "{}/{:L}"
> >> -      basic_stringstream<_CharT> __os2;
> >> -      __os2.imbue(__os.getloc());
> >> -      __os2 << __ymdl.year();
> >> -      if constexpr (is_same_v<_CharT, char>)
> >> -       __os2 << '/';
> >> -      else
> >> -       __os2 << L'/';
> >> -      __os2 << __ymdl.month_day_last();
> >> -      __os << __os2.view();
> >> -      return __os;
> >> +      return __detail::__chrono_write(__os, __ymdl, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits>
> >> @@ -3954,19 +3859,7 @@ namespace __detail
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os,
> >>                const year_month_weekday& __ymwd)
> >>      {
> >> -      // As above, just write straight to a stringstream, as if by
> >> -      // "{}/{:L}/{:L}"
> >> -      basic_stringstream<_CharT> __os2;
> >> -      __os2.imbue(__os.getloc());
> >> -      _CharT __slash;
> >> -      if constexpr (is_same_v<_CharT, char>)
> >> -       __slash = '/';
> >> -      else
> >> -       __slash = L'/';
> >> -      __os2 << __ymwd.year() << __slash << __ymwd.month() << __slash
> >> -           << __ymwd.weekday_indexed();
> >> -      __os << __os2.view();
> >> -      return __os;
> >> +      return __detail::__chrono_write(__os, __ymwd, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits>
> >> @@ -3974,19 +3867,7 @@ namespace __detail
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os,
> >>                const year_month_weekday_last& __ymwdl)
> >>      {
> >> -      // As above, just write straight to a stringstream, as if by
> >> -      // "{}/{:L}/{:L}"
> >> -      basic_stringstream<_CharT> __os2;
> >> -      __os2.imbue(__os.getloc());
> >> -      _CharT __slash;
> >> -      if constexpr (is_same_v<_CharT, char>)
> >> -       __slash = '/';
> >> -      else
> >> -       __slash = L'/';
> >> -      __os2 << __ymwdl.year() << __slash << __ymwdl.month() << __slash
> >> -           << __ymwdl.weekday_last();
> >> -      __os << __os2.view();
> >> -      return __os;
> >> +      return __detail::__chrono_write(__os, __ymwdl, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits, typename _Duration>
> >> @@ -3994,7 +3875,7 @@ namespace __detail
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os,
> >>                const hh_mm_ss<_Duration>& __hms)
> >>      {
> >> -      return __os << format(__os.getloc(), _GLIBCXX_WIDEN("{:L%T}"),
> >> __hms);
> >> +      return __detail::__chrono_write<64>(__os, __hms, __os.getloc());
> >>      }
> >>
> >>  #if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI
> >> @@ -4003,7 +3884,7 @@ namespace __detail
> >>      basic_ostream<_CharT, _Traits>&
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os, const sys_info&
> __i)
> >>      {
> >> -      return __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{}"),
> >> __i);
> >> +      return __detail::__chrono_write(__os, __i, __os.getloc());
> >>      }
> >>
> >>    /// Writes a local_info object to an ostream in an unspecified
> format.
> >> @@ -4011,20 +3892,7 @@ namespace __detail
> >>      basic_ostream<_CharT, _Traits>&
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os, const local_info&
> >> __li)
> >>      {
> >> -      __os << __format::_Separators<_CharT>::_S_squares()[0];
> >> -      if (__li.result == local_info::unique)
> >> -       __os << __li.first;
> >> -      else
> >> -       {
> >> -         if (__li.result == local_info::nonexistent)
> >> -           __os << _GLIBCXX_WIDEN("nonexistent");
> >> -         else
> >> -           __os << _GLIBCXX_WIDEN("ambiguous");
> >> -         __os << _GLIBCXX_WIDEN(" local time between ") << __li.first;
> >> -         __os << _GLIBCXX_WIDEN(" and ") << __li.second;
> >> -       }
> >> -      __os << __format::_Separators<_CharT>::_S_squares()[1];
> >> -      return __os;
> >> +      return __detail::__chrono_write<256>(__os, __li);
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits, typename _Duration,
> >> @@ -4033,8 +3901,7 @@ namespace __detail
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os,
> >>                const zoned_time<_Duration, _TimeZonePtr>& __t)
> >>      {
> >> -      __os << format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T %Z}"),
> __t);
> >> -      return __os;
> >> +      return __detail::__chrono_write(__os, __t, __os.getloc());
> >>      }
> >>  #endif
> >>
> >> @@ -4045,16 +3912,14 @@ namespace __detail
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os,
> >>                const sys_time<_Duration>& __tp)
> >>      {
> >> -      __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"),
> >> __tp);
> >> -      return __os;
> >> +      return __detail::__chrono_write<64>(__os, __tp, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits>
> >>      inline basic_ostream<_CharT, _Traits>&
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os, const sys_days&
> __dp)
> >>      {
> >> -      __os << year_month_day{__dp};
> >> -      return __os;
> >> +      return __detail::__chrono_write<32>(__os, __dp);
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits, typename _Duration,
> >> @@ -4090,8 +3955,7 @@ namespace __detail
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os,
> >>                const utc_time<_Duration>& __t)
> >>      {
> >> -      __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"),
> >> __t);
> >> -      return __os;
> >> +      return __detail::__chrono_write<64>(__os, __t, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits, typename _Duration,
> >> @@ -4125,8 +3989,7 @@ namespace __detail
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os,
> >>                const tai_time<_Duration>& __t)
> >>      {
> >> -      __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"),
> >> __t);
> >> -      return __os;
> >> +      return __detail::__chrono_write<64>(__os, __t, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits, typename _Duration,
> >> @@ -4164,8 +4027,7 @@ namespace __detail
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os,
> >>                const gps_time<_Duration>& __t)
> >>      {
> >> -      __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"),
> >> __t);
> >> -      return __os;
> >> +      return __detail::__chrono_write<64>(__os, __t, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits, typename _Duration,
> >> @@ -4202,8 +4064,7 @@ namespace __detail
> >>      operator<<(basic_ostream<_CharT, _Traits>& __os,
> >>                const file_time<_Duration>& __t)
> >>      {
> >> -      __os << std::format(__os.getloc(), _GLIBCXX_WIDEN("{:L%F %T}"),
> >> __t);
> >> -      return __os;
> >> +      return __detail::__chrono_write<64>(__os, __t, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits, typename _Duration,
> >> @@ -4228,8 +4089,7 @@ namespace __detail
> >>      // 4257. Stream insertion for chrono::local_time should be
> constrained
> >>      requires requires(const sys_time<_Duration>& __st) { __os << __st;
> }
> >>      {
> >> -      __os << sys_time<_Duration>{__lt.time_since_epoch()};
> >> -      return __os;
> >> +      return __detail::__chrono_write<64>(__os, __lt, __os.getloc());
> >>      }
> >>
> >>    template<typename _CharT, typename _Traits, typename _Duration,
> >> --
> >> 2.34.1
> >>
> >>
>
>

Reply via email to