Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[libc++][TZDB] Implements time_zone::to_sys. #90394

Merged
merged 1 commit into from
Jun 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libcxx/include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,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
Original file line number Diff line number Diff line change
@@ -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 <__chrono/calendar.h>
# include <__chrono/local_info.h>
# include <__chrono/time_point.h>
# include <__config>
# include <__configuration/availability.h>
# 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_NORETURN _LIBCPP_AVAILABILITY_TZDB _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_NORETURN _LIBCPP_AVAILABILITY_TZDB _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
27 changes: 27 additions & 0 deletions libcxx/include/__chrono/time_zone.h
Original file line number Diff line number Diff line change
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,31 @@ class _LIBCPP_AVAILABILITY_TZDB time_zone {
return __get_info(chrono::time_point_cast<seconds>(__time));
}

// We don't apply nodiscard here since this function throws on many inputs,
// so it could be used as a validation.
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
Original file line number Diff line number Diff line change
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 @@ -915,6 +923,7 @@ constexpr chrono::year operator ""y(unsigned lo
#if _LIBCPP_STD_VER >= 20
# include <__chrono/calendar.h>
# include <__chrono/day.h>
# include <__chrono/exception.h>
# include <__chrono/hh_mm_ss.h>
# include <__chrono/literals.h>
# include <__chrono/local_info.h>
Expand Down
6 changes: 5 additions & 1 deletion libcxx/include/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,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 @@ -1113,7 +1114,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
7 changes: 3 additions & 4 deletions libcxx/modules/std/chrono.inc
Original file line number Diff line number Diff line change
Expand Up @@ -209,13 +209,12 @@ export namespace std {
using std::chrono::reload_tzdb;
using std::chrono::remote_version;

# if 0
# endif // !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) &&
// !defined(_LIBCPP_HAS_NO_LOCALIZATION)

// [time.zone.exception], exception classes
using std::chrono::ambiguous_local_time;
using std::chrono::nonexistent_local_time;
# endif // if 0
# endif // !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) &&
// !defined(_LIBCPP_HAS_NO_LOCALIZATION)

// [time.zone.info], information classes
using std::chrono::local_info;
Expand Down
3 changes: 3 additions & 0 deletions libcxx/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,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
mordante marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
22 changes: 22 additions & 0 deletions libcxx/src/chrono_exception.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//===----------------------------------------------------------------------===//
//
// 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; // key function
_LIBCPP_AVAILABILITY_TZDB
_LIBCPP_EXPORTED_FROM_ABI ambiguous_local_time::~ambiguous_local_time() = default; // key function

} // namespace chrono

_LIBCPP_END_NAMESPACE_STD
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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;
}
Loading
Loading