llvm/libcxx/test/std/time/time.syn/formatter.hh_mm_ss.pass.cpp

//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: no-localization
// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME

// XFAIL: availability-fp_to_chars-missing

// REQUIRES: locale.fr_FR.UTF-8
// REQUIRES: locale.ja_JP.UTF-8

// <chrono>

// template<class Rep, class Period, class charT>
//   struct formatter<chrono::hh_mm_ss<duration<Rep, Period>>, charT>;

#include <chrono>
#include <format>

#include <cassert>
#include <concepts>
#include <locale>
#include <iostream>
#include <ratio>
#include <type_traits>

#include "formatter_tests.h"
#include "make_string.h"
#include "platform_support.h" // locale name macros
#include "string_literal.h"
#include "test_macros.h"

template <class CharT>
static void test_no_chrono_specs() {
  using namespace std::literals::chrono_literals;

  std::locale::global(std::locale(LOCALE_fr_FR_UTF_8));

  // Non localized output
  check(SV("00:00:00.000"), SV("{}"), std::chrono::hh_mm_ss{0ms});
  check(SV("*00:00:00.000*"), SV("{:*^14}"), std::chrono::hh_mm_ss{0ms});
  check(SV("*00:00:00.000"), SV("{:*>13}"), std::chrono::hh_mm_ss{0ms});

  std::locale::global(std::locale::classic());
}

template <class CharT>
static void test_valid_values() {
  using namespace std::literals::chrono_literals;

  constexpr std::basic_string_view<CharT> fmt = SV(
      "{:"
      "%%H='%H'%t"
      "%%OH='%OH'%t"
      "%%I='%I'%t"
      "%%OI='%OI'%t"
      "%%M='%M'%t"
      "%%OM='%OM'%t"
      "%%S='%S'%t"
      "%%OS='%OS'%t"
      "%%p='%p'%t"
      "%%R='%R'%t"
      "%%T='%T'%t"
      "%%r='%r'%t"
      "%%X='%X'%t"
      "%%EX='%EX'%t"
      "%n}");
  constexpr std::basic_string_view<CharT> lfmt = SV(
      "{:L"
      "%%H='%H'%t"
      "%%OH='%OH'%t"
      "%%I='%I'%t"
      "%%OI='%OI'%t"
      "%%M='%M'%t"
      "%%OM='%OM'%t"
      "%%S='%S'%t"
      "%%OS='%OS'%t"
      "%%p='%p'%t"
      "%%R='%R'%t"
      "%%T='%T'%t"
      "%%r='%r'%t"
      "%%X='%X'%t"
      "%%EX='%EX'%t"
      "%n}");

  const std::locale loc(LOCALE_ja_JP_UTF_8);
  std::locale::global(std::locale(LOCALE_fr_FR_UTF_8));

  // Non localized output using C-locale
  check(SV("%H='00'\t"
           "%OH='00'\t"
           "%I='12'\t"
           "%OI='12'\t"
           "%M='00'\t"
           "%OM='00'\t"
           "%S='00'\t"
           "%OS='00'\t"
           "%p='AM'\t"
           "%R='00:00'\t"
           "%T='00:00:00'\t"
           "%r='12:00:00 AM'\t"
           "%X='00:00:00'\t"
           "%EX='00:00:00'\t"
           "\n"),
        fmt,
        std::chrono::hh_mm_ss(0s));

  check(SV("%H='23'\t"
           "%OH='23'\t"
           "%I='11'\t"
           "%OI='11'\t"
           "%M='31'\t"
           "%OM='31'\t"
           "%S='30.123'\t"
           "%OS='30.123'\t"
           "%p='PM'\t"
           "%R='23:31'\t"
           "%T='23:31:30.123'\t"
           "%r='11:31:30 PM'\t"
           "%X='23:31:30'\t"
           "%EX='23:31:30'\t"
           "\n"),
        fmt,
        std::chrono::hh_mm_ss(23h + 31min + 30s + 123ms));

  check(SV("-%H='03'\t"
           "%OH='03'\t"
           "%I='03'\t"
           "%OI='03'\t"
           "%M='02'\t"
           "%OM='02'\t"
           "%S='01.123456789012'\t"
           "%OS='01.123456789012'\t"
           "%p='AM'\t"
           "%R='03:02'\t"
           "%T='03:02:01.123456789012'\t"
           "%r='03:02:01 AM'\t"
           "%X='03:02:01'\t"
           "%EX='03:02:01'\t"
           "\n"),
        fmt,
        std::chrono::hh_mm_ss(-(3h + 2min + 1s + std::chrono::duration<std::int64_t, std::pico>(123456789012))));

  // The number of fractional seconds is 0 according to the Standard
  // TODO FMT Determine what to do.
  check(SV("%H='01'\t"
           "%OH='01'\t"
           "%I='01'\t"
           "%OI='01'\t"
           "%M='01'\t"
           "%OM='01'\t"
           "%S='01'\t"
           "%OS='01'\t"
           "%p='AM'\t"
           "%R='01:01'\t"
           "%T='01:01:01'\t"
           "%r='01:01:01 AM'\t"
           "%X='01:01:01'\t"
           "%EX='01:01:01'\t"
           "\n"),
        fmt,
        std::chrono::hh_mm_ss(std::chrono::duration<double>(3661.123456)));

  // Use the global locale (fr_FR)
  check(SV("%H='00'\t"
           "%OH='00'\t"
           "%I='12'\t"
           "%OI='12'\t"
           "%M='00'\t"
           "%OM='00'\t"
           "%S='00'\t"
           "%OS='00'\t"
#if defined(_AIX)
           "%p='AM'\t"
#else
           "%p=''\t"
#endif
           "%R='00:00'\t"
           "%T='00:00:00'\t"
#ifdef _WIN32
           "%r='00:00:00'\t"
#elif defined(_AIX)
           "%r='12:00:00 AM'\t"
#elif defined(__APPLE__) || defined(__FreeBSD__)
           "%r=''\t"
#else
           "%r='12:00:00 '\t"
#endif
           "%X='00:00:00'\t"
           "%EX='00:00:00'\t"
           "\n"),
        lfmt,
        std::chrono::hh_mm_ss(0s));

  check(SV("%H='23'\t"
           "%OH='23'\t"
           "%I='11'\t"
           "%OI='11'\t"
           "%M='31'\t"
           "%OM='31'\t"
           "%S='30,123'\t"
           "%OS='30,123'\t"
#if defined(_AIX)
           "%p='PM'\t"
#else
           "%p=''\t"
#endif
           "%R='23:31'\t"
           "%T='23:31:30,123'\t"
#ifdef _WIN32
           "%r='23:31:30'\t"
#elif defined(_AIX)
           "%r='11:31:30 PM'\t"
#elif defined(__APPLE__) || defined(__FreeBSD__)
           "%r=''\t"
#else
           "%r='11:31:30 '\t"
#endif
           "%X='23:31:30'\t"
           "%EX='23:31:30'\t"
           "\n"),
        lfmt,
        std::chrono::hh_mm_ss(23h + 31min + 30s + 123ms));

  check(SV("-%H='03'\t"
           "%OH='03'\t"
           "%I='03'\t"
           "%OI='03'\t"
           "%M='02'\t"
           "%OM='02'\t"
           "%S='01,123456789012'\t"
           "%OS='01,123456789012'\t"
#if defined(_AIX)
           "%p='AM'\t"
#else
           "%p=''\t"
#endif
           "%R='03:02'\t"
           "%T='03:02:01,123456789012'\t"
#ifdef _WIN32
           "%r='03:02:01'\t"
#elif defined(_AIX)
           "%r='03:02:01 AM'\t"
#elif defined(__APPLE__) || defined(__FreeBSD__)
           "%r=''\t"
#else
           "%r='03:02:01 '\t"
#endif
           "%X='03:02:01'\t"
           "%EX='03:02:01'\t"
           "\n"),
        lfmt,
        std::chrono::hh_mm_ss(-(3h + 2min + 1s + std::chrono::duration<std::int64_t, std::pico>(123456789012))));

  check(SV("%H='01'\t"
           "%OH='01'\t"
           "%I='01'\t"
           "%OI='01'\t"
           "%M='01'\t"
           "%OM='01'\t"
           "%S='01'\t"
           "%OS='01'\t"
#if defined(_AIX)
           "%p='AM'\t"
#else
           "%p=''\t"
#endif
           "%R='01:01'\t"
           "%T='01:01:01'\t"
#ifdef _WIN32
           "%r='01:01:01'\t"
#elif defined(_AIX)
           "%r='01:01:01 AM'\t"
#elif defined(__APPLE__) || defined(__FreeBSD__)
           "%r=''\t"
#else
           "%r='01:01:01 '\t"
#endif
           "%X='01:01:01'\t"
           "%EX='01:01:01'\t"
           "\n"),
        lfmt,
        std::chrono::hh_mm_ss(std::chrono::duration<double>(3661.123456)));

  // Use supplied locale (ja_JP). This locale has a different alternate.
#if defined(__APPLE__) || defined(_AIX) || defined(_WIN32) || defined(__FreeBSD__)
  check(loc,
        SV("%H='00'\t"
           "%OH='00'\t"
           "%I='12'\t"
           "%OI='12'\t"
           "%M='00'\t"
           "%OM='00'\t"
           "%S='00'\t"
           "%OS='00'\t"
#  if defined(__APPLE__)
           "%p='AM'\t"
#  else
           "%p='午前'\t"
#  endif
           "%R='00:00'\t"
           "%T='00:00:00'\t"
#  if defined(__APPLE__) || defined(__FreeBSD__)
#    if defined(__APPLE__)
           "%r='12:00:00 AM'\t"
#    else
           "%r='12:00:00 午前'\t"
#    endif
           "%X='00時00分00秒'\t"
           "%EX='00時00分00秒'\t"
#  elif defined(_WIN32)
           "%r='0:00:00'\t"
           "%X='0:00:00'\t"
           "%EX='0:00:00'\t"
#  else
           "%r='午前12:00:00'\t"
           "%X='00:00:00'\t"
           "%EX='00:00:00'\t"
#  endif
           "\n"),
        lfmt,
        std::chrono::hh_mm_ss(0s));

  check(loc,
        SV("%H='23'\t"
           "%OH='23'\t"
           "%I='11'\t"
           "%OI='11'\t"
           "%M='31'\t"
           "%OM='31'\t"
           "%S='30.123'\t"
           "%OS='30.123'\t"
#  if defined(__APPLE__)
           "%p='PM'\t"
#  else
           "%p='午後'\t"
#  endif
           "%R='23:31'\t"
           "%T='23:31:30.123'\t"
#  if defined(__APPLE__) || defined(__FreeBSD__)
#    if defined(__APPLE__)
           "%r='11:31:30 PM'\t"
#    else
           "%r='11:31:30 午後'\t"
#    endif
           "%X='23時31分30秒'\t"
           "%EX='23時31分30秒'\t"
#  elif defined(_WIN32)
           "%r='23:31:30'\t"
           "%X='23:31:30'\t"
           "%EX='23:31:30'\t"
#  else
           "%r='午後11:31:30'\t"
           "%X='23:31:30'\t"
           "%EX='23:31:30'\t"
#  endif
           "\n"),
        lfmt,
        std::chrono::hh_mm_ss(23h + 31min + 30s + 123ms));

  check(loc,
        SV("-%H='03'\t"
           "%OH='03'\t"
           "%I='03'\t"
           "%OI='03'\t"
           "%M='02'\t"
           "%OM='02'\t"
           "%S='01.123456789012'\t"
           "%OS='01.123456789012'\t"
#  if defined(__APPLE__)
           "%p='AM'\t"
#  else
           "%p='午前'\t"
#  endif
           "%R='03:02'\t"
           "%T='03:02:01.123456789012'\t"
#  if defined(__APPLE__) || defined(__FreeBSD__)
#    if defined(__APPLE__)
           "%r='03:02:01 AM'\t"
#    else
           "%r='03:02:01 午前'\t"
#    endif
           "%X='03時02分01秒'\t"
           "%EX='03時02分01秒'\t"
#  elif defined(_WIN32)
           "%r='3:02:01'\t"
           "%X='3:02:01'\t"
           "%EX='3:02:01'\t"
#  else
           "%r='午前03:02:01'\t"
           "%X='03:02:01'\t"
           "%EX='03:02:01'\t"
#  endif
           "\n"),
        lfmt,
        std::chrono::hh_mm_ss(-(3h + 2min + 1s + std::chrono::duration<std::int64_t, std::pico>(123456789012))));

  check(loc,
        SV("%H='01'\t"
           "%OH='01'\t"
           "%I='01'\t"
           "%OI='01'\t"
           "%M='01'\t"
           "%OM='01'\t"
           "%S='01'\t"
           "%OS='01'\t"
#  if defined(__APPLE__)
           "%p='AM'\t"
#  else
           "%p='午前'\t"
#  endif
           "%R='01:01'\t"
           "%T='01:01:01'\t"
#  if defined(__APPLE__) || defined(__FreeBSD__)
#    if defined(__APPLE__)
           "%r='01:01:01 AM'\t"
#    else
           "%r='01:01:01 午前'\t"
#    endif
           "%X='01時01分01秒'\t"
           "%EX='01時01分01秒'\t"
#  elif defined(_WIN32)
           "%r='1:01:01'\t"
           "%X='1:01:01'\t"
           "%EX='1:01:01'\t"
#  else
           "%r='午前01:01:01'\t"
           "%X='01:01:01'\t"
           "%EX='01:01:01'\t"
#  endif
           "\n"),
        lfmt,
        std::chrono::hh_mm_ss(std::chrono::duration<double>(3661.123456)));
#else  // defined(__APPLE__) || defined(_AIX) || defined(_WIN32)
  check(loc,
        SV("%H='00'\t"
           "%OH='〇'\t"
           "%I='12'\t"
           "%OI='十二'\t"
           "%M='00'\t"
           "%OM='〇'\t"
           "%S='00'\t"
           "%OS='〇'\t"
           "%p='午前'\t"
           "%R='00:00'\t"
           "%T='00:00:00'\t"
           "%r='午前12時00分00秒'\t"
           "%X='00時00分00秒'\t"
           "%EX='00時00分00秒'\t"
           "\n"),
        lfmt,
        std::chrono::hh_mm_ss(0s));

  // TODO FMT What should fractions be in alternate display mode?
  check(loc,
        SV("%H='23'\t"
           "%OH='二十三'\t"
           "%I='11'\t"
           "%OI='十一'\t"
           "%M='31'\t"
           "%OM='三十一'\t"
           "%S='30.123'\t"
           "%OS='三十.123'\t"
           "%p='午後'\t"
           "%R='23:31'\t"
           "%T='23:31:30.123'\t"
           "%r='午後11時31分30秒'\t"
           "%X='23時31分30秒'\t"
           "%EX='23時31分30秒'\t"
           "\n"),
        lfmt,
        std::chrono::hh_mm_ss(23h + 31min + 30s + 123ms));

  check(loc,
        SV("-%H='03'\t"
           "%OH='三'\t"
           "%I='03'\t"
           "%OI='三'\t"
           "%M='02'\t"
           "%OM='二'\t"
           "%S='01.123456789012'\t"
           "%OS='一.123456789012'\t"
           "%p='午前'\t"
           "%R='03:02'\t"
           "%T='03:02:01.123456789012'\t"
           "%r='午前03時02分01秒'\t"
           "%X='03時02分01秒'\t"
           "%EX='03時02分01秒'\t"
           "\n"),
        lfmt,
        std::chrono::hh_mm_ss(-(3h + 2min + 1s + std::chrono::duration<std::int64_t, std::pico>(123456789012))));

  check(loc,
        SV("%H='01'\t"
           "%OH='一'\t"
           "%I='01'\t"
           "%OI='一'\t"
           "%M='01'\t"
           "%OM='一'\t"
           "%S='01'\t"
           "%OS='一'\t"
           "%p='午前'\t"
           "%R='01:01'\t"
           "%T='01:01:01'\t"
           "%r='午前01時01分01秒'\t"
           "%X='01時01分01秒'\t"
           "%EX='01時01分01秒'\t"
           "\n"),
        lfmt,
        std::chrono::hh_mm_ss(std::chrono::duration<double>(3661.123456)));
#endif // defined(__APPLE__) || defined(_AIX) || defined(_WIN32)

  std::locale::global(std::locale::classic());
}

template <class CharT>
static void test_invalid_values() {
  using namespace std::literals::chrono_literals;

  // This looks odd, however the 24 hours is not valid for a 24 hour clock.
  // TODO FMT discuss what the "proper" behaviour is.
  check_exception("Formatting a hour needs a valid value", SV("{:%H"), std::chrono::hh_mm_ss{24h});
  check_exception("Formatting a hour needs a valid value", SV("{:%OH"), std::chrono::hh_mm_ss{24h});
  check_exception("Formatting a hour needs a valid value", SV("{:%I"), std::chrono::hh_mm_ss{24h});
  check_exception("Formatting a hour needs a valid value", SV("{:%OI"), std::chrono::hh_mm_ss{24h});
  check(SV("00"), SV("{:%M}"), std::chrono::hh_mm_ss{24h});
  check(SV("00"), SV("{:%OM}"), std::chrono::hh_mm_ss{24h});
  check(SV("00"), SV("{:%S}"), std::chrono::hh_mm_ss{24h});
  check(SV("00"), SV("{:%OS}"), std::chrono::hh_mm_ss{24h});
  check_exception("Formatting a hour needs a valid value", SV("{:%p"), std::chrono::hh_mm_ss{24h});
  check_exception("Formatting a hour needs a valid value", SV("{:%R"), std::chrono::hh_mm_ss{24h});
  check_exception("Formatting a hour needs a valid value", SV("{:%T"), std::chrono::hh_mm_ss{24h});
  check_exception("Formatting a hour needs a valid value", SV("{:%r"), std::chrono::hh_mm_ss{24h});
  check_exception("Formatting a hour needs a valid value", SV("{:%X"), std::chrono::hh_mm_ss{24h});
  check_exception("Formatting a hour needs a valid value", SV("{:%EX"), std::chrono::hh_mm_ss{24h});
}

template <class CharT>
static void test() {
  using namespace std::literals::chrono_literals;

  test_no_chrono_specs<CharT>();
  test_valid_values<CharT>();
  test_invalid_values<CharT>();
  check_invalid_types<CharT>(
      {SV("H"),
       SV("I"),
       SV("M"),
       SV("S"),
       SV("p"),
       SV("r"),
       SV("R"),
       SV("T"),
       SV("X"),
       SV("OH"),
       SV("OI"),
       SV("OM"),
       SV("OS"),
       SV("EX")},
      std::chrono::hh_mm_ss{0ms});

  check_exception("The format specifier expects a '%' or a '}'", SV("{:A"), std::chrono::hh_mm_ss{0ms});
  check_exception("The chrono specifiers contain a '{'", SV("{:%%{"), std::chrono::hh_mm_ss{0ms});
  check_exception("End of input while parsing a conversion specifier", SV("{:%"), std::chrono::hh_mm_ss{0ms});
  check_exception("End of input while parsing the modifier E", SV("{:%E"), std::chrono::hh_mm_ss{0ms});
  check_exception("End of input while parsing the modifier O", SV("{:%O"), std::chrono::hh_mm_ss{0ms});

  check_exception("The format specifier expects a '%' or a '}'", SV("{:.3}"), std::chrono::hh_mm_ss{0ms});
}

int main(int, char**) {
  test<char>();

#ifndef TEST_HAS_NO_WIDE_CHARACTERS
  test<wchar_t>();
#endif

  return 0;
}