//===----------------------------------------------------------------------===//
//
// 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-filesystem, no-localization, no-tzdb

// XFAIL: libcpp-has-no-experimental-tzdb
// XFAIL: availability-tzdb-missing

// <chrono>

// class time_zone;

// template <class _Duration>
// sys_time<common_type_t<Duration, seconds>>
//   to_sys(const local_time<Duration>& tp) const;

#include <chrono>
#include <format>
#include <cassert>
#include <string_view>

#include "test_macros.h"
#include "assert_macros.h"
#include "concat_macros.h"

// Tests unique conversions. To make sure the test is does not depend on changes
// in the database it uses a time zone with a fixed offset.
static void test_unique() {
  using namespace std::literals::chrono_literals;

  const std::chrono::time_zone* tz = std::chrono::locate_zone("Etc/GMT+1");

  assert(tz->to_sys(std::chrono::local_time<std::chrono::nanoseconds>{-1ns}) ==
         std::chrono::sys_time<std::chrono::nanoseconds>{-1ns + 1h});

  assert(tz->to_sys(std::chrono::local_time<std::chrono::microseconds>{0us}) ==
         std::chrono::sys_time<std::chrono::microseconds>{1h});

  assert(tz->to_sys(std::chrono::local_time<std::chrono::seconds>{
             (std::chrono::sys_days{std::chrono::January / 1 / -21970}).time_since_epoch()}) ==
         std::chrono::sys_time<std::chrono::seconds>{
             (std::chrono::sys_days{std::chrono::January / 1 / -21970}).time_since_epoch() + 1h});

  // sys_time<common_type_t<Duration, seconds>> is seconds for the larger types
  assert(tz->to_sys(std::chrono::local_time<std::chrono::days>{
             (std::chrono::sys_days{std::chrono::January / 1 / 21970}).time_since_epoch()}) ==
         std::chrono::sys_time<std::chrono::seconds>{
             (std::chrono::sys_days{std::chrono::January / 1 / 21970}).time_since_epoch() + 1h});

  assert(tz->to_sys(std::chrono::local_time<std::chrono::weeks>{}) ==
         std::chrono::sys_time<std::chrono::seconds>{
             (std::chrono::sys_days{std::chrono::January / 1 / 1970}).time_since_epoch() + 1h});

  // Note months and years can not be streamed; thus the function cannot be
  // instantiated for these types. (Even when there is no exception thrown.)
}

// Tests non-existant conversions.
static void test_nonexistent() {
#ifndef TEST_HAS_NO_EXCEPTIONS
  using namespace std::literals::chrono_literals;

  const std::chrono::time_zone* tz = std::chrono::locate_zone("Europe/Berlin");

  // Z Europe/Berlin 0:53:28 - LMT 1893 Ap
  // ...
  // 1 DE CE%sT 1980
  // 1 E CE%sT
  //
  // ...
  // R E 1981 ma - Mar lastSu 1u 1 S
  // R E 1996 ma - O lastSu 1u 0 -

  // Pick an historic date where it's well known what the time zone rules were.
  // This makes it unlikely updates to the database change these rules.
  std::chrono::local_time<std::chrono::seconds> time{
      (std::chrono::sys_days{std::chrono::March / 30 / 1986} + 2h + 30min).time_since_epoch()};

  // Validates whether the database did not change.
  std::chrono::local_info info = tz->get_info(time);
  assert(info.result == std::chrono::local_info::nonexistent);

  TEST_VALIDATE_EXCEPTION(
      std::chrono::nonexistent_local_time,
      [&]([[maybe_unused]] const std::chrono::nonexistent_local_time& e) {
        std::string_view what =
            R"(1986-03-30 02:30:00.000000000 is in a gap between
1986-03-30 02:00:00 CET and
1986-03-30 03:00:00 CEST which are both equivalent to
1986-03-30 01:00:00 UTC)";
        TEST_LIBCPP_REQUIRE(
            e.what() == what,
            TEST_WRITE_CONCATENATED("Expected exception\n", what, "\n\nActual exception\n", e.what(), '\n'));
      },
      tz->to_sys(time + 0ns));

  TEST_VALIDATE_EXCEPTION(
      std::chrono::nonexistent_local_time,
      [&]([[maybe_unused]] const std::chrono::nonexistent_local_time& e) {
        std::string_view what =
            R"(1986-03-30 02:30:00.000000 is in a gap between
1986-03-30 02:00:00 CET and
1986-03-30 03:00:00 CEST which are both equivalent to
1986-03-30 01:00:00 UTC)";
        TEST_LIBCPP_REQUIRE(
            e.what() == what,
            TEST_WRITE_CONCATENATED("Expected exception\n", what, "\n\nActual exception\n", e.what(), '\n'));
      },
      tz->to_sys(time + 0us));

  TEST_VALIDATE_EXCEPTION(
      std::chrono::nonexistent_local_time,
      [&]([[maybe_unused]] const std::chrono::nonexistent_local_time& e) {
        std::string_view what =
            R"(1986-03-30 02:30:00.000 is in a gap between
1986-03-30 02:00:00 CET and
1986-03-30 03:00:00 CEST which are both equivalent to
1986-03-30 01:00:00 UTC)";
        TEST_LIBCPP_REQUIRE(
            e.what() == what,
            TEST_WRITE_CONCATENATED("Expected exception\n", what, "\n\nActual exception\n", e.what(), '\n'));
      },
      tz->to_sys(time + 0ms));

  TEST_VALIDATE_EXCEPTION(
      std::chrono::nonexistent_local_time,
      [&]([[maybe_unused]] const std::chrono::nonexistent_local_time& e) {
        std::string_view what =
            R"(1986-03-30 02:30:00 is in a gap between
1986-03-30 02:00:00 CET and
1986-03-30 03:00:00 CEST which are both equivalent to
1986-03-30 01:00:00 UTC)";
        TEST_LIBCPP_REQUIRE(
            e.what() == what,
            TEST_WRITE_CONCATENATED("Expected exception\n", what, "\n\nActual exception\n", e.what(), '\n'));
      },
      tz->to_sys(time + 0s));

#endif // TEST_HAS_NO_EXCEPTIONS
}

// Tests ambiguous conversions.
static void test_ambiguous() {
#ifndef TEST_HAS_NO_EXCEPTIONS
  using namespace std::literals::chrono_literals;

  const std::chrono::time_zone* tz = std::chrono::locate_zone("Europe/Berlin");

  // Z Europe/Berlin 0:53:28 - LMT 1893 Ap
  // ...
  // 1 DE CE%sT 1980
  // 1 E CE%sT
  //
  // ...
  // R E 1981 ma - Mar lastSu 1u 1 S
  // R E 1996 ma - O lastSu 1u 0 -

  // Pick an historic date where it's well known what the time zone rules were.
  // This makes it unlikely updates to the database change these rules.
  std::chrono::local_time<std::chrono::seconds> time{
      (std::chrono::sys_days{std::chrono::September / 28 / 1986} + 2h + 30min).time_since_epoch()};

  // Validates whether the database did not change.
  std::chrono::local_info info = tz->get_info(time);
  assert(info.result == std::chrono::local_info::ambiguous);

  TEST_VALIDATE_EXCEPTION(
      std::chrono::ambiguous_local_time,
      [&]([[maybe_unused]] const std::chrono::ambiguous_local_time& e) {
        std::string_view what =
            R"(1986-09-28 02:30:00.000000000 is ambiguous.  It could be
1986-09-28 02:30:00.000000000 CEST == 1986-09-28 00:30:00.000000000 UTC or
1986-09-28 02:30:00.000000000 CET == 1986-09-28 01:30:00.000000000 UTC)";
        TEST_LIBCPP_REQUIRE(
            e.what() == what,
            TEST_WRITE_CONCATENATED("Expected exception\n", what, "\n\nActual exception\n", e.what(), '\n'));
      },
      tz->to_sys(time + 0ns));

  TEST_VALIDATE_EXCEPTION(
      std::chrono::ambiguous_local_time,
      [&]([[maybe_unused]] const std::chrono::ambiguous_local_time& e) {
        std::string_view what =
            R"(1986-09-28 02:30:00.000000 is ambiguous.  It could be
1986-09-28 02:30:00.000000 CEST == 1986-09-28 00:30:00.000000 UTC or
1986-09-28 02:30:00.000000 CET == 1986-09-28 01:30:00.000000 UTC)";
        TEST_LIBCPP_REQUIRE(
            e.what() == what,
            TEST_WRITE_CONCATENATED("Expected exception\n", what, "\n\nActual exception\n", e.what(), '\n'));
      },
      tz->to_sys(time + 0us));

  TEST_VALIDATE_EXCEPTION(
      std::chrono::ambiguous_local_time,
      [&]([[maybe_unused]] const std::chrono::ambiguous_local_time& e) {
        std::string_view what =
            R"(1986-09-28 02:30:00.000 is ambiguous.  It could be
1986-09-28 02:30:00.000 CEST == 1986-09-28 00:30:00.000 UTC or
1986-09-28 02:30:00.000 CET == 1986-09-28 01:30:00.000 UTC)";
        TEST_LIBCPP_REQUIRE(
            e.what() == what,
            TEST_WRITE_CONCATENATED("Expected exception\n", what, "\n\nActual exception\n", e.what(), '\n'));
      },
      tz->to_sys(time + 0ms));

  TEST_VALIDATE_EXCEPTION(
      std::chrono::ambiguous_local_time,
      [&]([[maybe_unused]] const std::chrono::ambiguous_local_time& e) {
        std::string_view what =
            R"(1986-09-28 02:30:00 is ambiguous.  It could be
1986-09-28 02:30:00 CEST == 1986-09-28 00:30:00 UTC or
1986-09-28 02:30:00 CET == 1986-09-28 01:30:00 UTC)";
        TEST_LIBCPP_REQUIRE(
            e.what() == what,
            TEST_WRITE_CONCATENATED("Expected exception\n", what, "\n\nActual exception\n", e.what(), '\n'));
      },
      tz->to_sys(time + 0s));

#endif // TEST_HAS_NO_EXCEPTIONS
}

// This test does the basic validations of this function. The library function
// uses `local_info get_info(const local_time<Duration>& tp)` as implementation
// detail. The get_info function does extensive testing of the data.
int main(int, char**) {
  test_unique();
  test_nonexistent();
  test_ambiguous();

  return 0;
}
