Skip to content

Commit

Permalink
[libc++][TZDB] Implements time_zone::to_sys.
Browse files Browse the repository at this point in the history
This implements the throwing overload and the exception classes throw by
this overload.

Implements parts of:
- P0355 Extending chrono to Calendars and Time Zones
  • Loading branch information
mordante committed May 1, 2024
1 parent 6939905 commit fe9a4ce
Show file tree
Hide file tree
Showing 23 changed files with 985 additions and 44 deletions.
1 change: 1 addition & 0 deletions libcxx/include/CMakeLists.txt
Expand Up @@ -264,6 +264,7 @@ set(files
__chrono/convert_to_tm.h
__chrono/day.h
__chrono/duration.h
__chrono/exception.h
__chrono/file_clock.h
__chrono/formatter.h
__chrono/hh_mm_ss.h
Expand Down
129 changes: 129 additions & 0 deletions libcxx/include/__chrono/exception.h
@@ -0,0 +1,129 @@
// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// For information see https://libcxx.llvm.org/DesignDocs/TimeZone.html

#ifndef _LIBCPP___CHRONO_EXCEPTION_H
#define _LIBCPP___CHRONO_EXCEPTION_H

#include <version>
// Enable the contents of the header only when libc++ was built with experimental features enabled.
#if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB)

# include <__availability>
# include <__chrono/calendar.h>
# include <__chrono/local_info.h>
# include <__chrono/time_point.h>
# include <__config>
# include <__verbose_abort>
# include <format>
# include <stdexcept>
# include <string>

# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
# endif

_LIBCPP_BEGIN_NAMESPACE_STD

# if _LIBCPP_STD_VER >= 20

namespace chrono {

class nonexistent_local_time : public runtime_error {
public:
template <class _Duration>
_LIBCPP_HIDE_FROM_ABI nonexistent_local_time(const local_time<_Duration>& __time, const local_info& __info)
: runtime_error{__create_message(__time, __info)} {
// [time.zone.exception.nonexist]/2
// Preconditions: i.result == local_info::nonexistent is true.
// The value of __info.result is not used.
_LIBCPP_ASSERT_PEDANTIC(__info.result == local_info::nonexistent,
"creating an nonexistent_local_time from a local_info that is not non-existent");
}

_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI ~nonexistent_local_time() override; // exported as key function

private:
template <class _Duration>
_LIBCPP_HIDE_FROM_ABI string __create_message(const local_time<_Duration>& __time, const local_info& __info) {
return std::format(
R"({} is in a gap between
{} {} and
{} {} which are both equivalent to
{} UTC)",
__time,
local_seconds{__info.first.end.time_since_epoch()} + __info.first.offset,
__info.first.abbrev,
local_seconds{__info.second.begin.time_since_epoch()} + __info.second.offset,
__info.second.abbrev,
__info.first.end);
}
};

template <class _Duration>
_LIBCPP_AVAILABILITY_TZDB _LIBCPP_NORETURN _LIBCPP_HIDE_FROM_ABI void __throw_nonexistent_local_time(
[[maybe_unused]] const local_time<_Duration>& __time, [[maybe_unused]] const local_info& __info) {
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
throw nonexistent_local_time(__time, __info);
# else
_LIBCPP_VERBOSE_ABORT("nonexistent_local_time was thrown in -fno-exceptions mode");
# endif
}

class ambiguous_local_time : public runtime_error {
public:
template <class _Duration>
_LIBCPP_HIDE_FROM_ABI ambiguous_local_time(const local_time<_Duration>& __time, const local_info& __info)
: runtime_error{__create_message(__time, __info)} {
// [time.zone.exception.ambig]/2
// Preconditions: i.result == local_info::ambiguous is true.
// The value of __info.result is not used.
_LIBCPP_ASSERT_PEDANTIC(__info.result == local_info::ambiguous,
"creating an ambiguous_local_time from a local_info that is not ambiguous");
}

_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI ~ambiguous_local_time() override; // exported as key function

private:
template <class _Duration>
_LIBCPP_HIDE_FROM_ABI string __create_message(const local_time<_Duration>& __time, const local_info& __info) {
return std::format(
// There are two spaces after the full-stop; this has been verified
// in the sources of the Standard.
R"({0} is ambiguous. It could be
{0} {1} == {2} UTC or
{0} {3} == {4} UTC)",
__time,
__info.first.abbrev,
__time - __info.first.offset,
__info.second.abbrev,
__time - __info.second.offset);
}
};

template <class _Duration>
_LIBCPP_AVAILABILITY_TZDB _LIBCPP_NORETURN _LIBCPP_HIDE_FROM_ABI void __throw_ambiguous_local_time(
[[maybe_unused]] const local_time<_Duration>& __time, [[maybe_unused]] const local_info& __info) {
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
throw ambiguous_local_time(__time, __info);
# else
_LIBCPP_VERBOSE_ABORT("ambiguous_local_time was thrown in -fno-exceptions mode");
# endif
}

} // namespace chrono

# endif // _LIBCPP_STD_VER >= 20

_LIBCPP_END_NAMESPACE_STD

#endif // !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB)

#endif // _LIBCPP___CHRONO_EXCEPTION_H
26 changes: 26 additions & 0 deletions libcxx/include/__chrono/time_zone.h
Expand Up @@ -18,12 +18,14 @@

# include <__chrono/calendar.h>
# include <__chrono/duration.h>
# include <__chrono/exception.h>
# include <__chrono/local_info.h>
# include <__chrono/sys_info.h>
# include <__chrono/system_clock.h>
# include <__compare/strong_order.h>
# include <__config>
# include <__memory/unique_ptr.h>
# include <__type_traits/common_type.h>
# include <string_view>

# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
Expand Down Expand Up @@ -70,6 +72,30 @@ class _LIBCPP_AVAILABILITY_TZDB time_zone {
return __get_info(chrono::time_point_cast<seconds>(__time));
}

// Since the interface promisses throwing, don't add nodiscard.
template <class _Duration>
_LIBCPP_HIDE_FROM_ABI sys_time<common_type_t<_Duration, seconds>> to_sys(const local_time<_Duration>& __time) const {
local_info __info = get_info(__time);
switch (__info.result) {
case local_info::unique:
return sys_time<common_type_t<_Duration, seconds>>{__time.time_since_epoch() - __info.first.offset};

case local_info::nonexistent:
chrono::__throw_nonexistent_local_time(__time, __info);

case local_info::ambiguous:
chrono::__throw_ambiguous_local_time(__time, __info);
}

// TODO TZDB The Standard does not specify anything in these cases.
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
__info.result != -1, "cannot convert the local time; it would be before the minimum system clock value");
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
__info.result != -2, "cannot convert the local time; it would be after the maximum system clock value");

return {};
}

[[nodiscard]] _LIBCPP_HIDE_FROM_ABI const __impl& __implementation() const noexcept { return *__impl_; }

private:
Expand Down
9 changes: 9 additions & 0 deletions libcxx/include/chrono
Expand Up @@ -724,6 +724,10 @@ const time_zone* current_zone()
const tzdb& reload_tzdb(); // C++20
string remote_version(); // C++20
// [time.zone.exception], exception classes
class nonexistent_local_time; // C++20
class ambiguous_local_time; // C++20
// [time.zone.info], information classes
struct sys_info { // C++20
sys_seconds begin;
Expand Down Expand Up @@ -766,6 +770,10 @@ class time_zone {
template<class Duration>
local_info get_info(const local_time<Duration>& tp) const;
template<class Duration>
sys_time<common_type_t<Duration, seconds>>
to_sys(const local_time<Duration>& tp) const;
};
bool operator==(const time_zone& x, const time_zone& y) noexcept; // C++20
strong_ordering operator<=>(const time_zone& x, const time_zone& y) noexcept; // C++20
Expand Down Expand Up @@ -916,6 +924,7 @@ constexpr chrono::year operator ""y(unsigned lo
# include <__chrono/calendar.h>
# include <__chrono/day.h>
# include <__chrono/hh_mm_ss.h>
# include <__chrono/exception.h>
# include <__chrono/literals.h>
# include <__chrono/local_info.h>
# include <__chrono/month.h>
Expand Down
6 changes: 5 additions & 1 deletion libcxx/include/module.modulemap
Expand Up @@ -1112,6 +1112,7 @@ module std_private_chrono_duration [system] {
header "__chrono/duration.h"
export std_private_type_traits_is_convertible
}
module std_private_chrono_exception [system] { header "__chrono/exception.h" }
module std_private_chrono_file_clock [system] { header "__chrono/file_clock.h" }
module std_private_chrono_formatter [system] {
header "__chrono/formatter.h"
Expand All @@ -1124,7 +1125,10 @@ module std_private_chrono_high_resolution_clock [system] {
}
module std_private_chrono_leap_second [system] { header "__chrono/leap_second.h" }
module std_private_chrono_literals [system] { header "__chrono/literals.h" }
module std_private_chrono_local_info [system] { header "__chrono/local_info.h" }
module std_private_chrono_local_info [system] {
header "__chrono/local_info.h"
export std_private_chrono_sys_info
}
module std_private_chrono_month [system] { header "__chrono/month.h" }
module std_private_chrono_month_weekday [system] { header "__chrono/month_weekday.h" }
module std_private_chrono_monthday [system] { header "__chrono/monthday.h" }
Expand Down
2 changes: 0 additions & 2 deletions libcxx/modules/std/chrono.inc
Expand Up @@ -208,11 +208,9 @@ export namespace std {
using std::chrono::reload_tzdb;
using std::chrono::remote_version;

# if 0
// [time.zone.exception], exception classes
using std::chrono::ambiguous_local_time;
using std::chrono::nonexistent_local_time;
# endif // if 0

// [time.zone.info], information classes
using std::chrono::local_info;
Expand Down
3 changes: 3 additions & 0 deletions libcxx/src/CMakeLists.txt
Expand Up @@ -339,6 +339,9 @@ if (LIBCXX_ENABLE_LOCALIZATION AND LIBCXX_ENABLE_FILESYSTEM AND LIBCXX_ENABLE_TI
include/tzdb/types_private.h
include/tzdb/tzdb_list_private.h
include/tzdb/tzdb_private.h
# TODO TZDB The exception could be moved in chrono once the TZDB library
# is no longer experimental.
chrono_exception.cpp
time_zone.cpp
tzdb.cpp
tzdb_list.cpp
Expand Down
20 changes: 20 additions & 0 deletions libcxx/src/chrono_exception.cpp
@@ -0,0 +1,20 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include <chrono>

_LIBCPP_BEGIN_NAMESPACE_STD

namespace chrono {

_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI nonexistent_local_time::~nonexistent_local_time() = default;
_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI ambiguous_local_time::~ambiguous_local_time() = default;

} // namespace chrono

_LIBCPP_END_NAMESPACE_STD
@@ -0,0 +1,53 @@
//===----------------------------------------------------------------------===//
//
// 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

// REQUIRES: has-unix-headers
// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing

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

// <chrono>

// class ambiguous_local_time
//
// template<class Duration>
// ambiguous_local_time(const local_time<Duration>& tp, const local_info& i);

#include <chrono>

#include "check_assertion.h"

// [time.zone.exception.ambig]/2
// Preconditions: i.result == local_info::ambiguous is true.
int main(int, char**) {
TEST_LIBCPP_ASSERT_FAILURE(
(std::chrono::ambiguous_local_time{
std::chrono::local_seconds{},
std::chrono::local_info{-1, // this is not one of the "named" result values
std::chrono::sys_info{},
std::chrono::sys_info{}}}),
"creating an ambiguous_local_time from a local_info that is not ambiguous");

TEST_LIBCPP_ASSERT_FAILURE(
(std::chrono::ambiguous_local_time{
std::chrono::local_seconds{},
std::chrono::local_info{std::chrono::local_info::unique, std::chrono::sys_info{}, std::chrono::sys_info{}}}),
"creating an ambiguous_local_time from a local_info that is not ambiguous");

TEST_LIBCPP_ASSERT_FAILURE(
(std::chrono::ambiguous_local_time{
std::chrono::local_seconds{},
std::chrono::local_info{
std::chrono::local_info::nonexistent, std::chrono::sys_info{}, std::chrono::sys_info{}}}),
"creating an ambiguous_local_time from a local_info that is not ambiguous");

return 0;
}
@@ -0,0 +1,53 @@
//===----------------------------------------------------------------------===//
//
// 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

// REQUIRES: has-unix-headers
// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing

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

// <chrono>

// class nonexistent_local_time
//
// template<class Duration>
// nonexistent_local_time(const local_time<Duration>& tp, const local_info& i);

#include <chrono>

#include "check_assertion.h"

// [time.zone.exception.nonexist]/2
// Preconditions: i.result == local_info::nonexistent is true.
int main(int, char**) {
TEST_LIBCPP_ASSERT_FAILURE(
(std::chrono::nonexistent_local_time{
std::chrono::local_seconds{},
std::chrono::local_info{-1, // this is not one of the "named" result values
std::chrono::sys_info{},
std::chrono::sys_info{}}}),
"creating an nonexistent_local_time from a local_info that is not non-existent");

TEST_LIBCPP_ASSERT_FAILURE(
(std::chrono::nonexistent_local_time{
std::chrono::local_seconds{},
std::chrono::local_info{std::chrono::local_info::unique, std::chrono::sys_info{}, std::chrono::sys_info{}}}),
"creating an nonexistent_local_time from a local_info that is not non-existent");

TEST_LIBCPP_ASSERT_FAILURE(
(std::chrono::nonexistent_local_time{
std::chrono::local_seconds{},
std::chrono::local_info{
std::chrono::local_info::ambiguous, std::chrono::sys_info{}, std::chrono::sys_info{}}}),
"creating an nonexistent_local_time from a local_info that is not non-existent");

return 0;
}

0 comments on commit fe9a4ce

Please sign in to comment.