libstdc++: Implement C++20 time zone support in <chrono>

This is the largest missing piece of C++20 support. Only the cxx11 ABI
is supported, due to the use of std::string in the API for time zones.
For the old gcc4 ABI, utc_clock and leap seconds are supported, but only
using a hardcoded list of leap seconds, no up-to-date tzdb::leap_seconds
information is available, and no time zones or zoned_time conversions.

The implementation currently depends on a tzdata.zi file being provided
by the OS or the user. The expected location is /usr/share/zoneinfo but
that can be changed using --with-libstdcxx-zoneinfo-dir=PATH. On targets
that support it there is also a weak symbol that users can override in
their own program (which also helps with testing):

extern "C++" const char* __gnu_cxx::zoneinfo_dir_override();

If no file is found, a fallback tzdb object will be created which only
contains the "Etc/UTC" and "Etc/GMT" time zones.

A leapseconds file is also expected in the same directory, but if that
isn't present then a hardcoded list of leapseconds is used, which is
correct at least as far as 2023-06-28 (and it currently looks like no
leap second will be inserted for a few years).

The tzdata.zi and leapseconds files from https://www.iana.org/time-zones
are in the public domain, so shipping copies of them with GCC would be
an option. However, the tzdata.zi file will rapidly become outdated, so
users should really provide it themselves (or convince their OS vendor
to do so). It would also be possible to implement an alternative parser
for the compiled tzdata files (one per time zone) under
/usr/share/zoneinfo. Those files are present on more operating systems,
but do not contain all the information present in tzdata.zi.
Specifically, the "links" are not present, so that e.g. "UTC" and
"Universal" are distinct time zones, rather than both being links to the
canonical "Etc/UTC" zone. For some platforms those files are hard links
to the same file, but there's no indication which zone is the canonical
name and which is a link. Other platforms just store them in different
inodes anyway. I do not plan to add such an alternative parser for the
compiled files. That would need to be contributed by maintainers or
users of targets that require it, if making tzdata.zi available is not
an option. The library ABI would not need to change for a new tzdb
implementation, because everything in tzdb_list, tzdb and time_zone is
implemented as a pimpl (except for the shared_ptr links between nodes,
described below). That means the new exported symbols added by this
commit should be stable even if the implementation is completely
rewritten.

The information from tzdata.zi is parsed and stored in data structures
that closely model the info in the file. This is a space-efficient
representation that uses less memory that storing every transition for
every time zone.  It also avoids spending time expanding that
information into time zone transitions that might never be needed by the
program.  When a conversion to/from a local time to UTC is requested the
information will be processed to determine the time zone transitions
close to the time being converted.

There is a bug in some time zone transitions. When generating a sys_info
object immediately after one that was previously generated, we need to
find the previous rule that was in effect and note its offset and
letters. This is so that the start time and abbreviation of the new
sys_info will be correct. This only affects time zones that use a format
like "C%sT" where the LETTERS replacing %s are non-empty for standard
time, e.g. "Asia/Shanghai" which uses "CST" for standard time and "CDT"
for daylight time.

The tzdb_list structure maintains a linked list of tzdb nodes using
shared_ptr links. This allows the iterators into the list to share
ownership with the list itself. This offers a non-portable solution to a
lifetime issue in the API. Because tzdb objects can be erased from the
list using tzdb_list::erase_after, separate modules/libraries in a large
program cannot guarantee that any const tzdb& or const time_zone*
remains valid indefinitely. Holding onto a tzdb_list::const_iterator
will extend the tzdb object's lifetime, even if it's erased from the
list. An alternative design would be for the list iterator to hold a
weak_ptr. This would allow users to test whether the tzdb still exists
when the iterator is dereferenced, which is better than just having a
dangling raw pointer. That doesn't actually extend the tzdb's lifetime
though, and every use of it would need to be preceded by checking the
weak_ptr. Using shared_ptr adds a little bit of overhead but allows
users to solve the lifetime issue if they rely on the libstdc++-specific
iterator property.

libstdc++-v3/ChangeLog:

	* acinclude.m4 (GLIBCXX_ZONEINFO_DIR): New macro.
	* config.h.in: Regenerate.
	* config/abi/pre/gnu.ver: Export new symbols.
	* configure: Regenerate.
	* configure.ac (GLIBCXX_ZONEINFO_DIR): Use new macro.
	* include/std/chrono (utc_clock::from_sys): Correct handling
	of leap seconds.
	(nonexistent_local_time::_M_make_what_str): Define.
	(ambiguous_local_time::_M_make_what_str): Define.
	(__throw_bad_local_time): Define new function.
	(time_zone, tzdb_list, tzdb): Implement all members.
	(remote_version, zoned_time, get_leap_second_info): Define.
	* include/std/version: Add comment for __cpp_lib_chrono.
	* src/c++20/Makefile.am: Add new file.
	* src/c++20/Makefile.in: Regenerate.
	* src/c++20/tzdb.cc: New file.
	* testsuite/lib/libstdc++.exp: Define effective target tzdb.
	* testsuite/std/time/clock/file/members.cc: Check file_time
	alias and file_clock::now() member.
	* testsuite/std/time/clock/gps/1.cc: Likewise for gps_clock.
	* testsuite/std/time/clock/tai/1.cc: Likewise for tai_clock.
	* testsuite/std/time/syn_c++20.cc: Uncomment everything except
	parse.
	* testsuite/std/time/clock/utc/leap_second_info.cc: New test.
	* testsuite/std/time/exceptions.cc: New test.
	* testsuite/std/time/time_zone/get_info_local.cc: New test.
	* testsuite/std/time/time_zone/get_info_sys.cc: New test.
	* testsuite/std/time/time_zone/requirements.cc: New test.
	* testsuite/std/time/tzdb/1.cc: New test.
	* testsuite/std/time/tzdb/leap_seconds.cc: New test.
	* testsuite/std/time/tzdb_list/1.cc: New test.
	* testsuite/std/time/tzdb_list/requirements.cc: New test.
	* testsuite/std/time/zoned_time/1.cc: New test.
	* testsuite/std/time/zoned_time/custom.cc: New test.
	* testsuite/std/time/zoned_time/deduction.cc: New test.
	* testsuite/std/time/zoned_time/req_neg.cc: New test.
	* testsuite/std/time/zoned_time/requirements.cc: New test.
	* testsuite/std/time/zoned_traits.cc: New test.
This commit is contained in:
Jonathan Wakely 2022-12-22 00:37:54 +00:00
parent 907c84cb1d
commit 9fc61d45fa
30 changed files with 3884 additions and 186 deletions

View file

@ -5140,6 +5140,33 @@ AC_DEFUN([GLIBCXX_EMERGENCY_EH_ALLOC], [
AC_SUBST(EH_POOL_FLAGS)
])
dnl
dnl Allow the location of tzdata files to be configured.
dnl
dnl --with-libstdcxx-zoneinfo-dir=PATH will set the directory to PATH.
dnl
dnl Defines:
dnl _GLIBCXX_ZONEINFO_DIR if std::chrono::tzdb should use a non-default
dnl directory for the tzdata.zi and leapseconds files.
dnl
AC_DEFUN([GLIBCXX_ZONEINFO_DIR], [
AC_ARG_WITH([libstdcxx-zoneinfo-dir],
AC_HELP_STRING([--with-libstdcxx-zoneinfo-dir],
[the directory to search for tzdata files]),
[zoneinfo_dir="${withval}"
AC_DEFINE(_GLIBCXX_ZONEINFO_DIR, "${withval}",
[Define if a non-default location should be used for tzdata files.])
],
[
case "$host" in
# *-*-aix*) zoneinfo_dir="/usr/share/lib/zoneinfo" ;;
*) zoneinfo_dir="/usr/share/zoneinfo" ;;
esac
])
AC_MSG_NOTICE([zoneinfo data directory: ${zoneinfo_dir}])
])
# Macros from the top-level gcc directory.
m4_include([../config/gc++filt.m4])
m4_include([../config/tls.m4])

View file

@ -1037,6 +1037,9 @@
/* Defined if as can handle rdseed. */
#undef _GLIBCXX_X86_RDSEED
/* Define if a non-default location should be used for tzdata files. */
#undef _GLIBCXX_ZONEINFO_DIR
/* Define to 1 if mutex_timedlock is available. */
#undef _GTHREAD_USE_MUTEX_TIMEDLOCK

View file

@ -28,7 +28,8 @@ GLIBCXX_3.4 {
std::a[a-c]*;
std::ad[a-n]*;
std::ad[p-z]*;
std::a[e-z]*;
std::a[e-s]*;
std::a[u-z]*;
# std::ba[a-r]*;
std::basic_[a-e]*;
std::basic_f[a-h]*;
@ -2485,6 +2486,23 @@ GLIBCXX_3.4.31 {
_ZSt15__try_use_facet*;
_ZNSt6chrono11reload_tzdbEv;
_ZNSt6chrono8get_tzdbEv;
_ZNSt6chrono13get_tzdb_listEv;
_ZNSt6chrono14remote_version*;
_ZNSt6chrono12current_zoneEv;
_ZNSt6chrono11locate_zoneESt17basic_string_viewIcSt11char_traitsIcEE;
_ZNKSt6chrono9time_zone15_M_get_sys_info*;
_ZNKSt6chrono9time_zone17_M_get_local_info*;
_ZNKSt6chrono4tzdb12current_zoneEv;
_ZNKSt6chrono4tzdb11locate_zoneESt17basic_string_viewIcSt11char_traitsIcEE;
_ZNKSt6chrono9tzdb_list5beginEv;
_ZNKSt6chrono9tzdb_list5frontEv;
_ZNSt6chrono9tzdb_list11erase_afterENS0_14const_iteratorE;
_ZNKSt6chrono9tzdb_list14const_iteratordeEv;
_ZNSt6chrono9tzdb_list14const_iteratorppEv;
_ZNSt6chrono9tzdb_list14const_iteratorppEi;
} GLIBCXX_3.4.30;
# Symbols in the support library (libsupc++) have their own tag.

View file

@ -961,6 +961,7 @@ enable_libstdcxx_filesystem_ts
enable_libstdcxx_backtrace
enable_libstdcxx_static_eh_pool
with_libstdcxx_eh_pool_obj_count
with_libstdcxx_zoneinfo_dir
enable_cet
with_gxx_include_dir
enable_version_specific_runtime_libs
@ -1704,6 +1705,8 @@ Optional Packages:
--with-libstdcxx-eh-pool-obj-count
the number of exceptions that can be allocated from
the pool if malloc fails
--with-libstdcxx-zoneinfo-dir
the directory to search for tzdata files
--with-gxx-include-dir=DIR
installation directory for include files
--with-toolexeclibdir=DIR
@ -12182,7 +12185,7 @@ else
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
lt_status=$lt_dlunknown
cat > conftest.$ac_ext <<_LT_EOF
#line 12185 "configure"
#line 12188 "configure"
#include "confdefs.h"
#if HAVE_DLFCN_H
@ -12288,7 +12291,7 @@ else
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
lt_status=$lt_dlunknown
cat > conftest.$ac_ext <<_LT_EOF
#line 12291 "configure"
#line 12294 "configure"
#include "confdefs.h"
#if HAVE_DLFCN_H
@ -16012,7 +16015,7 @@ $as_echo "$glibcxx_cv_atomic_long_long" >&6; }
# Fake what AC_TRY_COMPILE does.
cat > conftest.$ac_ext << EOF
#line 16015 "configure"
#line 16018 "configure"
int main()
{
typedef bool atomic_type;
@ -16047,7 +16050,7 @@ $as_echo "$glibcxx_cv_atomic_bool" >&6; }
rm -f conftest*
cat > conftest.$ac_ext << EOF
#line 16050 "configure"
#line 16053 "configure"
int main()
{
typedef short atomic_type;
@ -16082,7 +16085,7 @@ $as_echo "$glibcxx_cv_atomic_short" >&6; }
rm -f conftest*
cat > conftest.$ac_ext << EOF
#line 16085 "configure"
#line 16088 "configure"
int main()
{
// NB: _Atomic_word not necessarily int.
@ -16118,7 +16121,7 @@ $as_echo "$glibcxx_cv_atomic_int" >&6; }
rm -f conftest*
cat > conftest.$ac_ext << EOF
#line 16121 "configure"
#line 16124 "configure"
int main()
{
typedef long long atomic_type;
@ -16274,7 +16277,7 @@ $as_echo "mutex" >&6; }
# unnecessary for this test.
cat > conftest.$ac_ext << EOF
#line 16277 "configure"
#line 16280 "configure"
int main()
{
_Decimal32 d1;
@ -16316,7 +16319,7 @@ ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
# unnecessary for this test.
cat > conftest.$ac_ext << EOF
#line 16319 "configure"
#line 16322 "configure"
template<typename T1, typename T2>
struct same
{ typedef T2 type; };
@ -71511,6 +71514,30 @@ fi
# For src/c++20/tzdb.cc defaults.
# Check whether --with-libstdcxx-zoneinfo-dir was given.
if test "${with_libstdcxx_zoneinfo_dir+set}" = set; then :
withval=$with_libstdcxx_zoneinfo_dir; zoneinfo_dir="${withval}"
$as_echo "#define _GLIBCXX_ZONEINFO_DIR \"\${withval}\"" >>confdefs.h
else
case "$host" in
# *-*-aix*) zoneinfo_dir="/usr/share/lib/zoneinfo" ;;
*) zoneinfo_dir="/usr/share/zoneinfo" ;;
esac
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: zoneinfo data directory: ${zoneinfo_dir}" >&5
$as_echo "$as_me: zoneinfo data directory: ${zoneinfo_dir}" >&6;}
# Define documentation rules conditionally.
# See if makeinfo has been installed and is modern enough

View file

@ -535,6 +535,9 @@ GLIBCXX_CHECK_EXCEPTION_PTR_SYMVER
# For libsupc++/eh_alloc.cc defaults.
GLIBCXX_EMERGENCY_EH_ALLOC
# For src/c++20/tzdb.cc defaults.
GLIBCXX_ZONEINFO_DIR
# Define documentation rules conditionally.
# See if makeinfo has been installed and is modern enough

View file

@ -46,11 +46,17 @@
# include <string>
# include <vector>
# include <bits/charconv.h> // __to_chars_len, __to_chars_10_impl
# include <bits/stl_algo.h> // upper_bound TODO: move leap_second_info to .so
# include <bits/stl_algo.h> // upper_bound
# include <bits/shared_ptr.h>
# include <bits/unique_ptr.h>
#endif
#if __cplusplus >= 202002L
// TODO formatting and parsing
// # undef __cpp_lib_chrono
// # define __cpp_lib_chrono 201907L
#endif
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
@ -140,7 +146,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
{
using _CDur = common_type_t<_Duration, seconds>;
const auto __li = chrono::get_leap_second_info(__t);
sys_time<_CDur> __s{__t.time_since_epoch() - seconds{__li.elapsed}};
sys_time<_CDur> __s{__t.time_since_epoch() - __li.elapsed};
if (__li.is_leap_second)
__s = chrono::floor<seconds>(__s) + seconds{1} - _CDur{1};
return __s;
@ -149,13 +155,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
template<typename _Duration>
[[nodiscard]]
static utc_time<common_type_t<_Duration, seconds>>
from_sys(const sys_time<_Duration>& __t)
{
using _CDur = common_type_t<_Duration, seconds>;
utc_time<_Duration> __u(__t.time_since_epoch());
const auto __li = chrono::get_leap_second_info(__u);
return utc_time<_CDur>{__u} + seconds{__li.elapsed};
}
from_sys(const sys_time<_Duration>& __t);
};
/** A clock that measures International Atomic Time.
@ -2056,7 +2056,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
- chrono::weekday{sys_days{_M_y / _M_m / 1}}
+ days((_M_wdi.index()-1)*7 + 1));
__glibcxx_assert(__d.count() >= 1);
return __d.count() <= unsigned{(_M_y / _M_m / last).day()};
return (unsigned)__d.count() <= (unsigned)(_M_y / _M_m / last).day();
}
friend constexpr bool
@ -2500,8 +2500,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
}
}
#if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI
// C++20 [time.zones] Time zones
struct tzdb;
struct sys_info
{
sys_seconds begin;
@ -2532,9 +2535,25 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
{ __glibcxx_assert(__i.result == local_info::nonexistent); }
private:
template<typename _Duration> // TODO
template<typename _Duration>
static string
_S_make_what_str(const local_time<_Duration>&, const local_info&);
_S_make_what_str(const local_time<_Duration>& __tp,
const local_info& __i)
{
#if 1
return "local time is non-existent";
#else
std::ostringstream __os;
__os << __tp << " is in a gap between\n"
<< local_seconds(__i.first.end.time_since_epoch())
+ __i.first.offset << ' ' << __i.first.abbrev << " and\n"
<< local_seconds(__i.second.begin.time_since_epoch())
+ __i.second.offset << ' ' << __i.second.abbrev
<< " which are both equivalent to\n"
<< __i.first.end << " UTC";
return std::move(__os).str();
#endif
}
};
class ambiguous_local_time : public runtime_error
@ -2542,16 +2561,44 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
public:
template<typename _Duration>
ambiguous_local_time(const local_time<_Duration>& __tp,
const local_info& __i)
const local_info& __i)
: runtime_error(_S_make_what_str(__tp, __i))
{ __glibcxx_assert(__i.result == local_info::nonexistent); }
{ __glibcxx_assert(__i.result == local_info::ambiguous); }
private:
template<typename _Duration> // TODO
template<typename _Duration>
static string
_S_make_what_str(const local_time<_Duration>&, const local_info&);
_S_make_what_str(const local_time<_Duration>& __tp,
const local_info& __i)
{
#if 1
return "local time is ambiguous";
#else
std::ostringstream __os;
__os << __tp << " is ambiguous. It could be\n"
<< __tp << ' ' << __i.first.abbrev << " == "
<< __tp - __i.first.offset << " UTC or\n"
<< __tp << ' ' << __i.second.abbrev << " == "
<< __tp - __i.second.offset << " UTC";
return std::move(__os).str();
#endif
}
};
template<typename _Duration>
[[noreturn]] void
__throw_bad_local_time(const local_time<_Duration>& __tp,
const local_info& __i)
{
#if __cpp_exceptions
if (__i.result == local_info::nonexistent)
throw nonexistent_local_time(__tp, __i);
throw ambiguous_local_time(__tp, __i);
#else
__builtin_abort();
#endif
}
enum class choose { earliest, latest };
class time_zone
@ -2560,46 +2607,188 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
time_zone(time_zone&&) = default;
time_zone& operator=(time_zone&&) = default;
~time_zone();
[[nodiscard]]
string_view name() const noexcept { return _M_name; }
template<typename _Duration>
sys_info
get_info(const sys_time<_Duration>& __st) const;
get_info(const sys_time<_Duration>& __st) const
{ return _M_get_sys_info(chrono::floor<seconds>(__st)); }
template<typename _Duration>
local_info
get_info(const local_time<_Duration>& __tp) const;
get_info(const local_time<_Duration>& __tp) const
{ return _M_get_local_info(chrono::floor<seconds>(__tp)); }
template<typename _Duration>
sys_time<common_type_t<_Duration, seconds>>
to_sys(const local_time<_Duration>& __tp) const;
to_sys(const local_time<_Duration>& __tp) const
{
local_info __info = get_info(__tp);
if (__info.result != local_info::unique)
__throw_bad_local_time(__tp, __info);
return sys_time<_Duration>(__tp.time_since_epoch())
- __info.first.offset;
}
template<typename _Duration>
sys_time<common_type_t<_Duration, seconds>>
to_sys(const local_time<_Duration>& __tp, choose __z) const;
to_sys(const local_time<_Duration>& __tp, choose __z) const
{
local_info __info = get_info(__tp);
if (__info.result == local_info::nonexistent)
return __info.first.end; // Last second of the previous sys_info.
sys_time<_Duration> __st(__tp.time_since_epoch());
if (__info.result == local_info::ambiguous && __z == choose::latest)
return __st - __info.second.offset; // Time in the later sys_info.
// else if __z == earliest, use __info.first.offset as below:
return __st - __info.first.offset;
}
template<typename _Duration>
local_time<common_type_t<_Duration, seconds>>
to_local(const sys_time<_Duration>& __tp) const;
to_local(const sys_time<_Duration>& __tp) const
{
auto __d = (__tp + get_info(__tp).offset).time_since_epoch();
return local_time<common_type_t<_Duration, seconds>>(__d);
}
friend bool
[[nodiscard]] friend bool
operator==(const time_zone& __x, const time_zone& __y) noexcept
{ return __x.name() == __y.name(); }
{ return __x._M_name == __y._M_name; }
friend strong_ordering
[[nodiscard]] friend strong_ordering
operator<=>(const time_zone& __x, const time_zone& __y) noexcept
{ return __x.name() <=> __y.name(); }
{ return __x._M_name <=> __y._M_name; }
private:
string _M_name;
sys_info _M_get_sys_info(sys_seconds) const;
local_info _M_get_local_info(local_seconds) const;
friend const tzdb& reload_tzdb();
friend struct tzdb;
friend class tzdb_list;
struct _Impl;
explicit time_zone(unique_ptr<_Impl> __p);
string _M_name;
unique_ptr<_Impl> _M_impl;
};
struct tzdb;
const time_zone* locate_zone(string_view __tz_name);
const time_zone* current_zone();
/** The list of `chrono::tzdb` objects
*
* A single object of this type is constructed by the C++ runtime,
* and can be accessed by calling `chrono::get_tzdb_list()`.
*
* The front of the list is the current `tzdb` object and can be accessed
* via `chrono::get_tzdb_list().front()` or `chrono::get_tzdb()` or
* `*chrono::get_tzdb_list().begin()`.
*
* The `chrono::reload_tzdb()` function will check for a newer version
* and if found, insert it at the front of the list.
*
* @since C++20
*/
class tzdb_list
{
struct _Node;
public:
tzdb_list(const tzdb_list&) = delete;
tzdb_list& operator=(const tzdb_list&) = delete;
/** An iterator into the `tzdb_list`
*
* As a extension, in libstdc++ each `tzdb` is reference-counted
* and the `const_iterator` type shares ownership of the object it
* refers to. This ensures that a `tzdb` erased from the list will
* not be destroyed while there is an iterator that refers to it.
*/
class const_iterator
{
public:
using value_type = tzdb;
using reference = const tzdb&;
using pointer = const tzdb*;
using difference_type = ptrdiff_t;
using iterator_category = forward_iterator_tag;
constexpr const_iterator() = default;
const_iterator(const const_iterator&) = default;
const_iterator(const_iterator&&) = default;
const_iterator& operator=(const const_iterator&) = default;
const_iterator& operator=(const_iterator&&) = default;
reference operator*() const noexcept;
pointer operator->() const noexcept { return &**this; }
const_iterator& operator++();
const_iterator operator++(int);
bool operator==(const const_iterator&) const noexcept = default;
private:
explicit const_iterator(const shared_ptr<_Node>&) noexcept;
friend class tzdb_list;
shared_ptr<_Node> _M_node;
void* _M_reserved = nullptr;
};
/** Access the current `tzdb` at the front of the list.
*
* This returns a reference to the same object as `chrono::get_tzdb()`.
*
* @returns A reference to the current tzdb object.
* @since C++20
*/
const tzdb& front() const noexcept;
/** Remove the tzdb object _after_ the one the iterator refers to.
*
* Calling this function concurently with any of `front()`, `begin()`,
* or `end()` does not cause a data race, but in general this function
* is not thread-safe. The behaviour may be undefined if erasing an
* element from the list while another thread is calling the same
* function, or incrementing an iterator into the list, or accessing
* the element being erased (unless it is accessed through an iterator).
*
* @param __p A dereferenceable iterator.
* @returns An iterator the element after the one that was erased
* (or `end()` if there is no such element).
* @since C++20
*/
const_iterator erase_after(const_iterator __p);
const_iterator begin() const noexcept;
const_iterator end() const noexcept { return {}; }
const_iterator cbegin() const noexcept { return begin(); }
const_iterator cend() const noexcept { return end(); }
private:
constexpr explicit tzdb_list(nullptr_t);
friend tzdb_list& get_tzdb_list();
friend const tzdb& get_tzdb();
friend const tzdb& reload_tzdb();
friend struct tzdb;
friend class leap_second;
friend struct time_zone::_Impl;
friend class time_zone_link;
};
class time_zone_link
{
public:
@ -2619,7 +2808,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
private:
friend const tzdb& reload_tzdb();
// TODO unspecified additional constructors
friend class tzdb_list::_Node;
explicit time_zone_link(nullptr_t) { }
string _M_name;
string _M_target;
};
@ -2720,10 +2912,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
private:
explicit leap_second(seconds::rep __s) : _M_s(__s) { }
friend class tzdb_list::_Node;
friend const tzdb& reload_tzdb();
template<typename _Dur>
template<typename _Duration>
friend leap_second_info
get_leap_second_info(const utc_time<_Dur>&);
get_leap_second_info(const utc_time<_Duration>&);
seconds _M_s; // == date().time_since_epoch() * value().count()
};
@ -2745,9 +2940,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
struct tzdb
{
string version;
vector<time_zone> zones;
vector<time_zone_link> links;
vector<leap_second> leap_seconds;
_GLIBCXX_STD_C::vector<time_zone> zones;
_GLIBCXX_STD_C::vector<time_zone_link> links;
_GLIBCXX_STD_C::vector<leap_second> leap_seconds;
const time_zone*
locate_zone(string_view __tz_name) const;
@ -2757,146 +2952,353 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
private:
friend const tzdb& reload_tzdb();
struct _Rule;
vector<_Rule> _M_rules;
friend class time_zone;
friend class tzdb_list::_Node;
};
class tzdb_list
{
struct _Node;
public:
tzdb_list(const tzdb_list&) = delete;
tzdb_list& operator=(const tzdb_list&) = delete;
tzdb_list& get_tzdb_list();
const tzdb& get_tzdb();
class const_iterator
{
public:
using value_type = tzdb;
using reference = const tzdb&;
using pointer = const tzdb*;
using difference_type = ptrdiff_t;
using iterator_category = forward_iterator_tag;
constexpr const_iterator() = default;
const_iterator(const const_iterator&) = default;
const_iterator(const_iterator&&) = default;
const_iterator& operator=(const const_iterator&) = default;
const_iterator& operator=(const_iterator&&) = default;
reference operator*() const noexcept;
pointer operator->() const noexcept { return &**this; }
const_iterator& operator++();
const_iterator operator++(int);
bool operator==(const const_iterator&) const noexcept = default;
private:
explicit const_iterator(const shared_ptr<_Node>&) noexcept;
shared_ptr<_Node> _M_node;
void* _M_reserved = nullptr;
};
// TODO const tzdb& front() const noexcept;
const_iterator erase_after(const_iterator);
const_iterator begin() const noexcept;
const_iterator end() const noexcept { return {}; }
const_iterator cbegin() const noexcept { return begin(); }
const_iterator cend() const noexcept { return end(); }
private:
constexpr explicit tzdb_list(nullptr_t);
friend const tzdb_list& get_tzdb_list();
friend const tzdb& get_tzdb();
friend const tzdb& reload_tzdb();
static _Node* _S_head;
static shared_ptr<_Node> _S_head_owner;
};
// TODO
// const tzdb_list& get_tzdb_list();
// const tzdb& get_tzdb();
// const tzdb& reload_tzdb();
// string remove_version();
const tzdb& reload_tzdb();
string remote_version();
template<typename _Duration, typename _TimeZonePtr = const time_zone*>
class zoned_time; // TODO
class zoned_time
{
static_assert(__is_duration_v<_Duration>);
using zoned_seconds = zoned_time<seconds>;
using _Traits = zoned_traits<_TimeZonePtr>;
// Every constructor that accepts a string_view as its first parameter
// does not participate in class template argument deduction.
using string_view = type_identity_t<std::string_view>;
public:
using duration = common_type_t<_Duration, seconds>;
zoned_time() requires requires { _Traits::default_zone(); }
{ }
zoned_time(const zoned_time&) = default;
zoned_time& operator=(const zoned_time&) = default;
zoned_time(const sys_time<_Duration>& __st)
requires requires { _Traits::default_zone(); }
: _M_tp(__st)
{ }
explicit
zoned_time(_TimeZonePtr __z) : _M_zone(std::move(__z)) { }
explicit
zoned_time(string_view __name)
requires requires {
_TimeZonePtr{_Traits::locate_zone(std::string_view{})};
}
: _M_zone(_Traits::locate_zone(__name))
{ }
template<typename _Duration2>
zoned_time(const zoned_time<_Duration2, _TimeZonePtr>& __zt)
requires is_convertible_v<sys_time<_Duration2>, sys_time<_Duration>>
: _M_zone(__zt._M_zone), _M_tp(__zt._M_tp)
{ }
zoned_time(_TimeZonePtr __z, const sys_time<_Duration>& __st)
: _M_zone(std::move(__z)), _M_tp(__st)
{ }
zoned_time(string_view __name, const sys_time<_Duration>& __st)
: zoned_time(_Traits::locate_zone(__name), __st)
{ }
zoned_time(_TimeZonePtr __z, const local_time<_Duration>& __tp)
requires requires {
{ __z->to_sys(__tp) } -> convertible_to<sys_time<_Duration>>;
}
: _M_zone(std::move(__z)), _M_tp(_M_zone->to_sys(__tp))
{ }
zoned_time(string_view __name, const local_time<_Duration>& __tp)
requires requires (_TimeZonePtr __z) {
{ _Traits::locate_zone(__name) } -> convertible_to<_TimeZonePtr>;
{ __z->to_sys(__tp) } -> convertible_to<sys_time<_Duration>>;
}
: zoned_time(_Traits::locate_zone(__name), __tp)
{ }
zoned_time(_TimeZonePtr __z, const local_time<_Duration>& __tp,
choose __c)
requires requires {
{ __z->to_sys(__tp, __c) } -> convertible_to<sys_time<_Duration>>;
}
: _M_zone(std::move(__z)), _M_tp(_M_zone->to_sys(__tp, __c))
{ }
zoned_time(string_view __name, const local_time<_Duration>& __tp,
choose __c)
requires requires (_TimeZonePtr __z) {
{ _Traits::locate_zone(__name) } -> convertible_to<_TimeZonePtr>;
{ __z->to_sys(__tp, __c) } -> convertible_to<sys_time<_Duration>>;
}
: _M_zone(_Traits::locate_zone(__name)),
_M_tp(_M_zone->to_sys(__tp, __c))
{ }
template<typename _Duration2, typename _TimeZonePtr2>
zoned_time(_TimeZonePtr __z,
const zoned_time<_Duration2, _TimeZonePtr2>& __zt)
requires is_convertible_v<sys_time<_Duration2>, sys_time<_Duration>>
: _M_zone(__z), _M_tp(__zt._M_tp)
{ }
template<typename _Duration2, typename _TimeZonePtr2>
zoned_time(_TimeZonePtr __z,
const zoned_time<_Duration2, _TimeZonePtr2>& __zt,
choose __c)
requires is_convertible_v<sys_time<_Duration2>, sys_time<_Duration>>
: _M_zone(__z), _M_tp(__zt._M_tp)
{ }
template<typename _Duration2, typename _TimeZonePtr2>
zoned_time(string_view __name,
const zoned_time<_Duration2, _TimeZonePtr2>& __zt)
requires is_convertible_v<sys_time<_Duration2>, sys_time<_Duration>>
&& requires {
{ _Traits::locate_zone(__name) } -> convertible_to<_TimeZonePtr>;
}
: _M_zone(_Traits::locate_zone(__name)), _M_tp(__zt._M_tp)
{ }
template<typename _Duration2, typename _TimeZonePtr2>
zoned_time(string_view __name,
const zoned_time<_Duration2, _TimeZonePtr2>& __zt,
choose __c)
requires is_convertible_v<sys_time<_Duration2>, sys_time<_Duration>>
&& requires {
{ _Traits::locate_zone(__name) } -> convertible_to<_TimeZonePtr>;
}
: _M_zone(_Traits::locate_zone(__name)), _M_tp(__zt._M_tp)
{ }
zoned_time&
operator=(const sys_time<_Duration>& __st)
{
_M_tp = __st;
return *this;
}
zoned_time&
operator=(const local_time<_Duration>& __lt)
{
_M_tp = _M_zone->to_sys(__lt);
return *this;
}
[[nodiscard]]
operator sys_time<duration>() const { return _M_tp; }
[[nodiscard]]
explicit operator local_time<duration>() const
{ return get_local_time(); }
[[nodiscard]]
_TimeZonePtr
get_time_zone() const
{ return _M_zone; }
[[nodiscard]]
local_time<duration>
get_local_time() const
{ return _M_zone->to_local(_M_tp); }
[[nodiscard]]
sys_time<duration>
get_sys_time() const
{ return _M_tp; }
[[nodiscard]]
sys_info
get_info() const
{ return _M_zone->get_info(_M_tp); }
[[nodiscard]] friend bool
operator==(const zoned_time&, const zoned_time&) = default;
private:
_TimeZonePtr _M_zone{ _Traits::default_zone() };
sys_time<duration> _M_tp{};
template<typename _Duration2, typename _TimeZonePtr2>
friend class zoned_time;
};
zoned_time() -> zoned_time<seconds>;
template<typename _Duration>
leap_second_info
zoned_time(sys_time<_Duration>)
-> zoned_time<common_type_t<_Duration, seconds>>;
/// @cond undocumented
template<typename _TimeZonePtrOrName>
using __time_zone_representation
= __conditional_t<is_convertible_v<_TimeZonePtrOrName, string_view>,
const time_zone*,
remove_cvref_t<_TimeZonePtrOrName>>;
/// @endcond
template<typename _TimeZonePtrOrName>
zoned_time(_TimeZonePtrOrName&&)
-> zoned_time<seconds, __time_zone_representation<_TimeZonePtrOrName>>;
template<typename _TimeZonePtrOrName, typename _Duration>
zoned_time(_TimeZonePtrOrName&&, sys_time<_Duration>)
-> zoned_time<common_type_t<_Duration, seconds>,
__time_zone_representation<_TimeZonePtrOrName>>;
template<typename _TimeZonePtrOrName, typename _Duration>
zoned_time(_TimeZonePtrOrName&&, local_time<_Duration>,
choose = choose::earliest)
-> zoned_time<common_type_t<_Duration, seconds>,
__time_zone_representation<_TimeZonePtrOrName>>;
template<typename _Duration, typename _TimeZonePtrOrName,
typename _TimeZonePtr2>
zoned_time(_TimeZonePtrOrName&&, zoned_time<_Duration, _TimeZonePtr2>,
choose = choose::earliest)
-> zoned_time<common_type_t<_Duration, seconds>,
__time_zone_representation<_TimeZonePtrOrName>>;
template<typename _Dur1, typename _TZPtr1, typename _Dur2, typename _TZPtr2>
[[nodiscard]]
inline bool
operator==(const zoned_time<_Dur1, _TZPtr1>& __x,
const zoned_time<_Dur2, _TZPtr2>& __y)
{
return __x.get_time_zone() == __y.get_time_zone()
&& __x.get_sys_time() == __y.get_sys_time();
}
using zoned_seconds = zoned_time<seconds>;
#endif // _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI
namespace __detail
{
inline leap_second_info
__get_leap_second_info(sys_seconds __ss, bool __is_utc)
{
if (__ss < sys_seconds{}) [[unlikely]]
return {};
const seconds::rep __leaps[] {
78796800, // 1 Jul 1972
94694400, // 1 Jan 1973
126230400, // 1 Jan 1974
157766400, // 1 Jan 1975
189302400, // 1 Jan 1976
220924800, // 1 Jan 1977
252460800, // 1 Jan 1978
283996800, // 1 Jan 1979
315532800, // 1 Jan 1980
362793600, // 1 Jul 1981
394329600, // 1 Jul 1982
425865600, // 1 Jul 1983
489024000, // 1 Jul 1985
567993600, // 1 Jan 1988
631152000, // 1 Jan 1990
662688000, // 1 Jan 1991
709948800, // 1 Jul 1992
741484800, // 1 Jul 1993
773020800, // 1 Jul 1994
820454400, // 1 Jan 1996
867715200, // 1 Jul 1997
915148800, // 1 Jan 1999
1136073600, // 1 Jan 2006
1230768000, // 1 Jan 2009
1341100800, // 1 Jul 2012
1435708800, // 1 Jul 2015
1483228800, // 1 Jan 2017
};
// The list above is known to be valid until (at least) this date
// and only contains positive leap seconds.
const sys_seconds __expires(1687910400s); // 2023-06-28 00:00:00 UTC
#if _GLIBCXX_USE_CXX11_ABI || ! _GLIBCXX_USE_DUAL_ABI
if (__ss > __expires)
{
// Use updated leap_seconds from tzdb.
size_t __n = std::size(__leaps);
auto __db = get_tzdb_list().begin();
auto __first = __db->leap_seconds.begin() + __n;
auto __last = __db->leap_seconds.end();
auto __pos = std::upper_bound(__first, __last, __ss);
seconds __elapsed(__n);
for (auto __i = __first; __i != __pos; ++__i)
__elapsed += __i->value();
if (__is_utc)
{
// Convert utc_time to sys_time:
__ss -= __elapsed;
// See if that sys_time is before (or during) previous leap sec:
if (__pos != __first && __ss < __pos[-1])
{
if ((__ss + 1s) >= __pos[-1])
return {true, __elapsed};
__elapsed -= __pos[-1].value();
}
}
return {false, __elapsed};
}
else
#endif
{
seconds::rep __s = __ss.time_since_epoch().count();
const seconds::rep* __first = std::begin(__leaps);
const seconds::rep* __last = std::end(__leaps);
// Don't bother searching the list if we're after the last one.
if (__s > (__last[-1] + (__last - __first) + 1))
return { false, seconds(__last - __first) };
auto __pos = std::upper_bound(__first, __last, __s);
seconds __elapsed{__pos - __first};
if (__is_utc)
{
// Convert utc_time to sys_time:
__s -= __elapsed.count();
// See if that sys_time is before (or during) previous leap sec:
if (__pos != __first && __s < __pos[-1])
{
if ((__s + 1) >= __pos[-1])
return {true, __elapsed};
--__elapsed;
}
}
return {false, __elapsed};
}
}
} // namespace __detail
template<typename _Duration>
[[nodiscard]]
inline leap_second_info
get_leap_second_info(const utc_time<_Duration>& __ut)
{
if constexpr (is_same_v<_Duration, seconds>)
{
const seconds::rep __leaps[] {
78796800, // 1 Jul 1972
94694400, // 1 Jan 1973
126230400, // 1 Jan 1974
157766400, // 1 Jan 1975
189302400, // 1 Jan 1976
220924800, // 1 Jan 1977
252460800, // 1 Jan 1978
283996800, // 1 Jan 1979
315532800, // 1 Jan 1980
362793600, // 1 Jul 1981
394329600, // 1 Jul 1982
425865600, // 1 Jul 1983
489024000, // 1 Jul 1985
567993600, // 1 Jan 1988
631152000, // 1 Jan 1990
662688000, // 1 Jan 1991
709948800, // 1 Jul 1992
741484800, // 1 Jul 1993
773020800, // 1 Jul 1994
820454400, // 1 Jan 1996
867715200, // 1 Jul 1997
915148800, // 1 Jan 1999
1136073600, // 1 Jan 2006
1230768000, // 1 Jan 2009
1341100800, // 1 Jul 2012
1435708800, // 1 Jul 2015
1483228800, // 1 Jan 2017
};
// The list above is known to be valid until 2023-06-28 00:00:00 UTC
const seconds::rep __expires = 1687910400;
const seconds::rep __s = __ut.time_since_epoch().count();
auto __s = chrono::duration_cast<seconds>(__ut.time_since_epoch());
return __detail::__get_leap_second_info(sys_seconds(__s), true);
}
const seconds::rep* __first = std::begin(__leaps);
const seconds::rep* __last = std::end(__leaps);
if (__s > __expires)
{
// TODO: use updated leap_seconds from tzdb
#if 0
auto __db = get_tzdb_list().begin();
__first = __db->leap_seconds.data();
__last = __first + __db->leap_seconds.size();
#endif
}
// Don't bother searching the list if we're after the last one.
if (__s > __last[-1])
return { false, seconds(__last - __first) };
auto __pos = std::upper_bound(__first, __last, __s);
return {
__pos != begin(__leaps) && __pos[-1] == __s,
seconds{__pos - __first}
};
}
else
{
auto __s = chrono::time_point_cast<seconds>(__ut);
return chrono::get_leap_second_info(__s);
}
template<typename _Duration>
[[nodiscard]]
inline utc_time<common_type_t<_Duration, seconds>>
utc_clock::from_sys(const sys_time<_Duration>& __t)
{
using _CDur = common_type_t<_Duration, seconds>;
auto __s = chrono::time_point_cast<seconds>(__t);
const auto __li = __detail::__get_leap_second_info(__s, false);
return utc_time<_CDur>{__t.time_since_epoch()} + __li.elapsed;
}
/// @} group chrono

View file

@ -251,6 +251,8 @@
# define __cpp_lib_barrier 201907L
# endif
#endif
// #undef __cpp_lib_chrono
// #define __cpp_lib_chrono 201907L
// FIXME: #define __cpp_lib_execution 201902L
#define __cpp_lib_constexpr_algorithms 201806L
#ifdef __cpp_lib_is_constant_evaluated

View file

@ -36,7 +36,7 @@ else
inst_sources =
endif
sources =
sources = tzdb.cc
vpath % $(top_srcdir)/src/c++20

View file

@ -121,7 +121,7 @@ CONFIG_CLEAN_FILES =
CONFIG_CLEAN_VPATH_FILES =
LTLIBRARIES = $(noinst_LTLIBRARIES)
libc__20convenience_la_LIBADD =
am__objects_1 =
am__objects_1 = tzdb.lo
@ENABLE_EXTERN_TEMPLATE_TRUE@am__objects_2 = sstream-inst.lo
am_libc__20convenience_la_OBJECTS = $(am__objects_1) $(am__objects_2)
libc__20convenience_la_OBJECTS = $(am_libc__20convenience_la_OBJECTS)
@ -431,7 +431,7 @@ headers =
@ENABLE_EXTERN_TEMPLATE_TRUE@inst_sources = \
@ENABLE_EXTERN_TEMPLATE_TRUE@ sstream-inst.cc
sources =
sources = tzdb.cc
libc__20convenience_la_SOURCES = $(sources) $(inst_sources)
# AM_CXXFLAGS needs to be in each subdirectory so that it can be

File diff suppressed because it is too large Load diff

View file

@ -1397,6 +1397,20 @@ proc check_effective_target_hosted { } {
}]
}
# Return 1 if std::chrono::tzdb is supported.
proc check_effective_target_tzdb { } {
if {![check_effective_target_cxx11_abi]} {
return 0
}
return [check_v3_target_prop_cached et_tzdb {
set cond "defined _GLIBCXX_ZONEINFO_DIR"
if {[v3_check_preprocessor_condition tzdb $cond]} {
return 1
}
return [file exists /usr/share/zoneinfo/tzdata.zi]
}]
}
set additional_prunes ""
if { [info exists env(GCC_RUNTEST_PARALLELIZE_DIR)] \

View file

@ -32,8 +32,19 @@ test01()
VERIFY( d2 == d1 );
}
void
test02()
{
using namespace std::chrono;
file_time<file_clock::duration> t = file_clock::now();
file_time<seconds> s = floor<seconds>(t);
VERIFY( t - s < 1s );
}
int
main()
{
test01();
test02();
}

View file

@ -31,8 +31,26 @@ test02()
VERIFY( clock_cast<gps_clock>(clock_cast<utc_clock>(t)) == t );
}
void
test03()
{
using namespace std::chrono;
gps_time<gps_clock::duration> gps1 = gps_clock::now();
utc_time<utc_clock::duration> utc = utc_clock::now();
gps_time<gps_clock::duration> gps2 = gps_clock::now();
auto delta = gps2 - gps1;
VERIFY( (utc - clock_cast<utc_clock>(gps1)) <= delta );
VERIFY( (clock_cast<utc_clock>(gps2) - utc) <= delta );
gps_seconds s = time_point_cast<seconds>(gps1);
VERIFY( gps1 - s < 1s );
}
int main()
{
test01();
test02();
test03();
}

View file

@ -34,8 +34,26 @@ test02()
VERIFY( clock_cast<tai_clock>(clock_cast<utc_clock>(t)) == t );
}
void
test03()
{
using namespace std::chrono;
tai_time<tai_clock::duration> tai1 = tai_clock::now();
utc_time<utc_clock::duration> utc = utc_clock::now();
tai_time<tai_clock::duration> tai2 = tai_clock::now();
auto delta = tai2 - tai1;
VERIFY( (utc - clock_cast<utc_clock>(tai1)) <= delta );
VERIFY( (clock_cast<utc_clock>(tai2) - utc) <= delta );
tai_seconds s = time_point_cast<seconds>(tai1);
VERIFY( tai1 - s < 1s );
}
int main()
{
test01();
test02();
test03();
}

View file

@ -0,0 +1,80 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
// { dg-additional-options "-DHAVE_TZDB" { target tzdb } }
#include <chrono>
#include <testsuite_hooks.h>
using namespace std::chrono_literals;
void
test_before()
{
// No leaps seconds defined before the epoch.
auto s = std::chrono::utc_seconds(-1s);
auto lsi = get_leap_second_info(s);
VERIFY( lsi.is_leap_second == false );
VERIFY( lsi.elapsed == 0s );
auto ms = std::chrono::utc_time<std::chrono::milliseconds>(s - 5500ms);
lsi = get_leap_second_info(ms);
VERIFY( lsi.is_leap_second == false );
VERIFY( lsi.elapsed == 0s );
}
void
test_after()
{
#ifdef HAVE_TZDB
const auto& leaps = std::chrono::get_tzdb().leap_seconds;
std::chrono::seconds sum(0);
for (auto leap : leaps)
sum += leap.value();
// After the last defined leap second.
auto last = leaps.back().date().time_since_epoch();
auto ut = std::chrono::utc_time<std::chrono::milliseconds>(last + 72h + 10ms);
auto lsi = get_leap_second_info(ut);
VERIFY( lsi.is_leap_second == false );
VERIFY( lsi.elapsed == sum );
#endif
}
void
test_between()
{
std::chrono::sys_days st(1995y/9/4);
auto ut = std::chrono::clock_cast<std::chrono::utc_clock>(st);
auto lsi = get_leap_second_info(ut);
VERIFY( lsi.is_leap_second == false );
VERIFY( lsi.elapsed == 19s );
}
void
test_during()
{
#ifdef HAVE_TZDB
// Verify that leap_second_info::is_leap_second is true for each leap second.
const auto& leaps = std::chrono::get_tzdb().leap_seconds;
for (const auto& leap : leaps)
{
// N.B. this assumes all leap seconds are positive:
std::chrono::seconds elapsed(&leap - &leaps.front());
std::chrono::utc_seconds ut(leap.date().time_since_epoch() + elapsed);
auto lsi = get_leap_second_info(ut);
VERIFY( lsi.is_leap_second == true );
VERIFY( lsi.elapsed == elapsed + 1s );
lsi = get_leap_second_info(ut + 999ms);
VERIFY( lsi.is_leap_second == true );
VERIFY( lsi.elapsed == elapsed + 1s );
}
#endif
}
int main()
{
test_before();
test_after();
test_between();
test_during();
}

View file

@ -0,0 +1,49 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
// { dg-require-effective-target tzdb }
#include <chrono>
#include <testsuite_hooks.h>
void
test_nonexistent()
{
std::string expected
= "2016-03-13 02:30:00 is in a gap between\n"
"2016-03-13 02:00:00 EST and\n"
"2016-03-13 03:00:00 EDT which are both equivalent to\n"
"2016-03-13 07:00:00 UTC";
using namespace std::chrono;
try {
auto zt = zoned_time{"America/New_York",
local_days{Sunday[2]/March/2016} + 2h + 30min};
VERIFY(false);
} catch (const nonexistent_local_time& e) {
// VERIFY( e.what() == expected );
}
}
void
test_ambiguous()
{
std::string expected
= "2016-11-06 01:30:00 is ambiguous. It could be\n"
"2016-11-06 01:30:00 EDT == 2016-11-06 05:30:00 UTC or\n"
"2016-11-06 01:30:00 EST == 2016-11-06 06:30:00 UTC";
using namespace std::chrono;
try {
auto zt = zoned_time{"America/New_York",
local_days{Sunday[1]/November/2016} + 1h + 30min};
VERIFY(false);
} catch (const ambiguous_local_time& e) {
// VERIFY( e.what() == expected );
}
}
int main()
{
test_nonexistent();
test_ambiguous();
}

View file

@ -43,8 +43,6 @@ namespace __gnu_test
using std::chrono::sys_seconds;
using std::chrono::sys_days;
// FIXME
#if 0
using std::chrono::utc_clock;
using std::chrono::utc_time;
using std::chrono::utc_seconds;
@ -59,7 +57,6 @@ namespace __gnu_test
using std::chrono::gps_clock;
using std::chrono::gps_time;
using std::chrono::gps_seconds;
#endif
using std::chrono::file_clock;
using std::chrono::file_time;
@ -69,13 +66,10 @@ namespace __gnu_test
using std::chrono::local_seconds;
using std::chrono::local_days;
// FIXME
#if 0
using std::chrono::clock_time_conversion;
using std::chrono::clock_cast;
using std::chrono::last_spec;
#endif
using std::chrono::day;
using std::chrono::month;
@ -101,8 +95,7 @@ namespace __gnu_test
using std::chrono::make12;
using std::chrono::make24;
// FIXME
#if 0
#if _GLIBCXX_USE_CXX11_ABI
using std::chrono::tzdb;
using std::chrono::tzdb_list;
using std::chrono::get_tzdb;
@ -129,12 +122,14 @@ namespace __gnu_test
using std::chrono::leap_second;
using std::chrono::time_zone_link;
using std::chrono::local_time_format;
using std::chrono::parse;
#endif
// FIXME
// using std::chrono::local_time_format;
// FIXME
// using std::chrono::parse;
using std::chrono::last;
using std::chrono::Sunday;
using std::chrono::Monday;

View file

@ -0,0 +1,222 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
// { dg-require-effective-target tzdb }
#include <chrono>
#include <testsuite_hooks.h>
using namespace std::chrono;
struct empty_tag { } empty;
bool operator==(const sys_info& info, empty_tag)
{
return info.begin == sys_seconds() && info.end == info.begin
&& info.offset == 0s && info.save == 0min && info.abbrev.empty();
}
void
test_utc()
{
auto tz = locate_zone("UTC");
auto now = time_point_cast<seconds>(system_clock::now());
local_info info = tz->get_info(local_seconds(now.time_since_epoch()));
VERIFY( info.result == local_info::unique );
VERIFY( info.first.begin < now );
VERIFY( info.first.end > now );
VERIFY( info.first.offset == 0h );
VERIFY( info.first.save == 0h );
VERIFY( info.first.abbrev == "UTC" );
VERIFY( info.second == empty );
}
auto dst_start = March/Sunday[last];
auto dst_end = October/Sunday[last];
void
test_unique()
{
auto tz = locate_zone("Europe/London");
local_days feb1(sys_days(2022y/February/1).time_since_epoch());
local_info info;
info = tz->get_info(feb1);
VERIFY( info.result == local_info::unique );
VERIFY( info.first.begin == sys_days(2021y/dst_end) + 1h );
VERIFY( info.first.end == sys_days(2022y/dst_start) + 1h );
VERIFY( info.first.offset == 0h );
VERIFY( info.first.save == 0h );
VERIFY( info.first.abbrev == "GMT" );
VERIFY( info.second == empty );
info = tz->get_info(feb1 + months(4));
VERIFY( info.result == local_info::unique );
VERIFY( info.first.begin == sys_days(2022y/dst_start) + 1h );
VERIFY( info.first.end == sys_days(2022y/dst_end) + 1h );
VERIFY( info.first.offset == 1h );
VERIFY( info.first.save == 1h );
VERIFY( info.first.abbrev == "BST" );
VERIFY( info.second == empty );
}
void
test_nonexistent()
{
auto tz = locate_zone("Europe/Helsinki");
sys_time<hours> change = sys_days(2022y/dst_start) + 1h;
local_seconds nonesuch(change.time_since_epoch() + 2h + 30min);
local_info info;
info = tz->get_info(nonesuch);
VERIFY( info.result == local_info::nonexistent );
VERIFY( info.first.end == change );
VERIFY( info.first.offset == 2h );
VERIFY( info.first.save == 0h );
VERIFY( info.first.abbrev == "EET" );
VERIFY( info.second.begin == info.first.end );
VERIFY( info.second.offset == 3h );
VERIFY( info.second.save == 1h );
VERIFY( info.second.abbrev == "EEST" );
tz = locate_zone("America/New_York");
nonesuch = local_days(Sunday[2]/March/2016) + 2h + 30min;
info = tz->get_info(nonesuch);
VERIFY( info.result == local_info::nonexistent );
VERIFY( info.first.end == sys_days(Sunday[2]/March/2016) + 5h + 2h );
VERIFY( info.first.offset == -5h );
VERIFY( info.first.save == 0h );
VERIFY( info.first.abbrev == "EST" );
VERIFY( info.second.begin == info.first.end );
VERIFY( info.second.offset == -4h );
VERIFY( info.second.save == 1h );
VERIFY( info.second.abbrev == "EDT" );
}
void
test_ambiguous()
{
auto tz = locate_zone("Europe/Helsinki");
sys_time<hours> change = sys_days(2022y/dst_end) + 1h;
local_seconds twix(change.time_since_epoch() + 2h + 30min);
local_info info;
info = tz->get_info(twix);
VERIFY( info.result == local_info::ambiguous );
VERIFY( info.first.end == change );
VERIFY( info.first.offset == 3h );
VERIFY( info.first.save == 1h );
VERIFY( info.first.abbrev == "EEST" );
VERIFY( info.second.begin == info.first.end );
VERIFY( info.second.offset == 2h );
VERIFY( info.second.save == 0h );
VERIFY( info.second.abbrev == "EET" );
tz = locate_zone("America/New_York");
twix = local_days(Sunday[2]/March/2016) + 2h + 30min;
info = tz->get_info(twix);
VERIFY( info.result == local_info::nonexistent );
VERIFY( info.first.end == sys_days(Sunday[2]/March/2016) + 5h + 2h );
VERIFY( info.first.offset == -5h );
VERIFY( info.first.save == 0h );
VERIFY( info.first.abbrev == "EST" );
VERIFY( info.second.begin == info.first.end );
VERIFY( info.second.offset == -4h );
VERIFY( info.second.save == 1h );
VERIFY( info.second.abbrev == "EDT" );
}
void
test_egypt()
{
local_days d(2010y/May/1);
auto tz = locate_zone("Egypt");
local_info info = tz->get_info(d);
VERIFY( info.result == local_info::unique );
VERIFY( info.first.begin == sys_days(2010y/April/29) + 22h );
VERIFY( info.first.offset == 3h );
VERIFY( info.first.save == 1h );
VERIFY( info.first.abbrev == "EEST" );
info = tz->get_info(d - 24h);
VERIFY( info.result == local_info::nonexistent );
VERIFY( info.first.begin == sys_days(2009y/August/20) + 21h );
VERIFY( info.first.offset == 2h );
VERIFY( info.first.save == 0h );
VERIFY( info.first.abbrev == "EET" );
VERIFY( info.second.begin == sys_days(2010y/April/29) + 22h );
VERIFY( info.second.offset == 3h );
VERIFY( info.second.save == 1h );
VERIFY( info.second.abbrev == "EEST" );
#if 0
std::ostringstream out;
local_seconds lt(local_days(2001y/January/1));
const local_days end(2021y/January/1);
while (lt < end)
{
local_info i = tz->get_info(lt);
out << '\n' << i;
auto next = i.first.end;
if (i.result != local_info::unique)
next = i.second.begin + 24h;
lt = zoned_time(tz, next).get_local_time();
}
out << '\n';
std::string expected = R"(
[[2000-09-28 21:00:00,2001-04-26 22:00:00,02:00:00,0min,EET]]
[[2001-04-26 22:00:00,2001-09-27 21:00:00,03:00:00,60min,EEST]]
[ambiguous local time between [2001-04-26 22:00:00,2001-09-27 21:00:00,03:00:00,60min,EEST] and [2001-09-27 21:00:00,2002-04-25 22:00:00,02:00:00,0min,EET]]
[[2001-09-27 21:00:00,2002-04-25 22:00:00,02:00:00,0min,EET]]
[[2002-04-25 22:00:00,2002-09-26 21:00:00,03:00:00,60min,EEST]]
[ambiguous local time between [2002-04-25 22:00:00,2002-09-26 21:00:00,03:00:00,60min,EEST] and [2002-09-26 21:00:00,2003-04-24 22:00:00,02:00:00,0min,EET]]
[[2002-09-26 21:00:00,2003-04-24 22:00:00,02:00:00,0min,EET]]
[[2003-04-24 22:00:00,2003-09-25 21:00:00,03:00:00,60min,EEST]]
[ambiguous local time between [2003-04-24 22:00:00,2003-09-25 21:00:00,03:00:00,60min,EEST] and [2003-09-25 21:00:00,2004-04-29 22:00:00,02:00:00,0min,EET]]
[[2003-09-25 21:00:00,2004-04-29 22:00:00,02:00:00,0min,EET]]
[[2004-04-29 22:00:00,2004-09-30 21:00:00,03:00:00,60min,EEST]]
[ambiguous local time between [2004-04-29 22:00:00,2004-09-30 21:00:00,03:00:00,60min,EEST] and [2004-09-30 21:00:00,2005-04-28 22:00:00,02:00:00,0min,EET]]
[[2004-09-30 21:00:00,2005-04-28 22:00:00,02:00:00,0min,EET]]
[[2005-04-28 22:00:00,2005-09-29 21:00:00,03:00:00,60min,EEST]]
[ambiguous local time between [2005-04-28 22:00:00,2005-09-29 21:00:00,03:00:00,60min,EEST] and [2005-09-29 21:00:00,2006-04-27 22:00:00,02:00:00,0min,EET]]
[[2005-09-29 21:00:00,2006-04-27 22:00:00,02:00:00,0min,EET]]
[[2006-04-27 22:00:00,2006-09-21 21:00:00,03:00:00,60min,EEST]]
[ambiguous local time between [2006-04-27 22:00:00,2006-09-21 21:00:00,03:00:00,60min,EEST] and [2006-09-21 21:00:00,2007-04-26 22:00:00,02:00:00,0min,EET]]
[[2006-09-21 21:00:00,2007-04-26 22:00:00,02:00:00,0min,EET]]
[[2007-04-26 22:00:00,2007-09-06 21:00:00,03:00:00,60min,EEST]]
[ambiguous local time between [2007-04-26 22:00:00,2007-09-06 21:00:00,03:00:00,60min,EEST] and [2007-09-06 21:00:00,2008-04-24 22:00:00,02:00:00,0min,EET]]
[[2007-09-06 21:00:00,2008-04-24 22:00:00,02:00:00,0min,EET]]
[[2008-04-24 22:00:00,2008-08-28 21:00:00,03:00:00,60min,EEST]]
[ambiguous local time between [2008-04-24 22:00:00,2008-08-28 21:00:00,03:00:00,60min,EEST] and [2008-08-28 21:00:00,2009-04-23 22:00:00,02:00:00,0min,EET]]
[[2008-08-28 21:00:00,2009-04-23 22:00:00,02:00:00,0min,EET]]
[[2009-04-23 22:00:00,2009-08-20 21:00:00,03:00:00,60min,EEST]]
[ambiguous local time between [2009-04-23 22:00:00,2009-08-20 21:00:00,03:00:00,60min,EEST] and [2009-08-20 21:00:00,2010-04-29 22:00:00,02:00:00,0min,EET]]
[[2009-08-20 21:00:00,2010-04-29 22:00:00,02:00:00,0min,EET]]
[[2010-04-29 22:00:00,2010-08-10 21:00:00,03:00:00,60min,EEST]]
[ambiguous local time between [2010-04-29 22:00:00,2010-08-10 21:00:00,03:00:00,60min,EEST] and [2010-08-10 21:00:00,2010-09-09 22:00:00,02:00:00,0min,EET]]
[[2010-08-10 21:00:00,2010-09-09 22:00:00,02:00:00,0min,EET]]
[[2010-09-09 22:00:00,2010-09-30 21:00:00,03:00:00,60min,EEST]]
[ambiguous local time between [2010-09-09 22:00:00,2010-09-30 21:00:00,03:00:00,60min,EEST] and [2010-09-30 21:00:00,2014-05-15 22:00:00,02:00:00,0min,EET]]
[[2010-09-30 21:00:00,2014-05-15 22:00:00,02:00:00,0min,EET]]
[[2014-05-15 22:00:00,2014-06-26 21:00:00,03:00:00,60min,EEST]]
[ambiguous local time between [2014-05-15 22:00:00,2014-06-26 21:00:00,03:00:00,60min,EEST] and [2014-06-26 21:00:00,2014-07-31 22:00:00,02:00:00,0min,EET]]
[[2014-06-26 21:00:00,2014-07-31 22:00:00,02:00:00,0min,EET]]
[[2014-07-31 22:00:00,2014-09-25 21:00:00,03:00:00,60min,EEST]]
[ambiguous local time between [2014-07-31 22:00:00,2014-09-25 21:00:00,03:00:00,60min,EEST] and [2014-09-25 21:00:00,32767-12-31 00:00:00,02:00:00,0min,EET]]
[[2014-09-25 21:00:00,32767-12-31 00:00:00,02:00:00,0min,EET]]
)";
VERIFY( out.str() == expected );
#endif
}
int main()
{
test_utc();
test_unique();
test_nonexistent();
test_ambiguous();
test_egypt();
}

View file

@ -0,0 +1,219 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
// { dg-require-effective-target tzdb }
#include <chrono>
#include <testsuite_hooks.h>
void
test_zurich()
{
using namespace std::chrono;
const time_zone* const tz = locate_zone("Europe/Zurich");
{
sys_days d = 1853y/July/16;
auto info = tz->get_info(d - 1s);
VERIFY( info.offset == (34min + 8s) );
VERIFY( info.abbrev == "LMT" );
info = tz->get_info(d);
VERIFY( info.offset == (29min + 46s) );
VERIFY( info.abbrev == "BMT" );
info = tz->get_info(d + 1s);
VERIFY( info.offset == (29min + 46s) );
VERIFY( info.abbrev == "BMT" );
info = tz->get_info(d + 0.001s);
VERIFY( info.offset == (29min + 46s) );
VERIFY( info.abbrev == "BMT" );
}
{
sys_days d = 1894y/June/1;
auto info = tz->get_info(d - 1s);
VERIFY( info.offset == (29min + 46s) );
VERIFY( info.abbrev == "BMT" );
info = tz->get_info(d);
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
}
{
sys_days d = 1941y/May/Monday[1];
auto info = tz->get_info(d - 1s);
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
// CEST daylight savings time starts at 1am local time (UTC+1).
info = tz->get_info(d);
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
}
{
sys_days d = 1941y/October/Monday[1];
auto info = tz->get_info(d - 1s);
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
// CET standard time starts at 2am local time (UTC+2).
info = tz->get_info(d);
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
}
{
sys_days d = 1942y/May/Monday[1];
auto info = tz->get_info(d - 1s);
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
// CEST daylight savings time starts at 1am local time (UTC+1).
info = tz->get_info(d);
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
}
{
sys_days d = 1942y/October/Monday[1];
auto info = tz->get_info(d - 1s);
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
// CET standard time starts at 2am local time (UTC+2).
info = tz->get_info(d);
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
}
{
sys_days d = 1943y/May/Monday[1];
// No daylight savings from 1943 until 1981.
auto info = tz->get_info(d);
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
info = tz->get_info(d + days(60));
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
info = tz->get_info(d + years(10));
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
info = tz->get_info(sys_days(1979y/June/3));
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
}
{
// Switzerland uses EU rules from 1981
sys_days d = 1981y/March/Sunday[last];
auto info = tz->get_info(d);
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
info = tz->get_info(d + 59min + 59s);
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
// CEST begins at 1am UTC
info = tz->get_info(d + 1h);
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
}
{
sys_days d = 1981y/September/Sunday[last];
auto info = tz->get_info(d + 59min + 59s);
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
// CEST ends at 1am UTC
info = tz->get_info(d + 1h);
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
}
{
sys_days d = 1994y/September/Sunday[last];
auto info = tz->get_info(d + 59min + 59s);
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
// CEST ends at 1am UTC
info = tz->get_info(d + 1h);
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
d = 1995y/September/Sunday[last];
info = tz->get_info(d + 59min + 59s);
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
// CEST ends at 1am UTC
info = tz->get_info(d + 1h);
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
d = 1996y/September/Sunday[last];
// CEST ends in October since 1996
info = tz->get_info(d + 1h);
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
d = 1996y/October/Sunday[last];
// CEST ends at 1am UTC
info = tz->get_info(d + 1h);
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
}
}
void
test_iterate()
{
using namespace std::chrono;
auto tz = locate_zone("Europe/Zurich");
sys_seconds start(sys_days(1850y/January/1));
const sys_seconds finish(sys_days(1982y/January/1));
long count = 0;
do
{
VERIFY(++count < 100); // Fail if we get stuck in a loop.
auto info = tz->get_info(start);
start = info.end;
} while (start < finish);
VERIFY(count == 10); // Would be 9 if identical adjacent sys_info get merged.
}
void
test_shanghai()
{
using namespace std::chrono;
auto tz = locate_zone("Asia/Shanghai");
sys_info info = tz->get_info(sys_days(1949y/January/1));
VERIFY( info.abbrev == "CST" );
}
int main()
{
test_zurich();
test_iterate();
test_shanghai();
}

View file

@ -0,0 +1,25 @@
// { dg-options "-std=gnu++20" }
// { dg-do compile { target c++20 } }
// { dg-require-effective-target cxx11_abi }
#include <chrono>
using std::chrono::time_zone;
static_assert( std::is_move_constructible_v<time_zone> );
static_assert( std::is_move_assignable_v<time_zone> );
static_assert( ! std::is_default_constructible_v<time_zone> );
static_assert( ! std::is_copy_constructible_v<time_zone> );
static_assert( ! std::is_copy_assignable_v<time_zone> );
extern const time_zone* tz;
static_assert( std::is_same_v<decltype(tz->name()), std::string_view> );
static_assert( noexcept(tz->name()) );
static_assert( std::is_same_v<decltype(*tz == *tz), bool> );
static_assert( noexcept(*tz == *tz) );
static_assert( std::is_same_v<decltype(*tz <=> *tz), std::strong_ordering> );
static_assert( noexcept(*tz <=> *tz) );

View file

@ -0,0 +1,56 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
// { dg-require-effective-target cxx11_abi }
// { dg-additional-options "-DHAVE_TZDB" { target tzdb } }
#include <chrono>
#include <testsuite_hooks.h>
using namespace std::chrono;
void
test_version()
{
const tzdb& db = get_tzdb();
VERIFY( &db == &get_tzdb_list().front() );
#ifdef HAVE_TZDB
VERIFY( db.version == remote_version() );
const tzdb& reloaded = reload_tzdb();
if (reloaded.version == db.version)
VERIFY( &reloaded == &db );
#endif
}
void
test_current()
{
#ifdef HAVE_TZDB
const tzdb& db = get_tzdb();
const time_zone* tz = db.current_zone();
VERIFY( tz == std::chrono::current_zone() );
#endif
}
void
test_locate()
{
const tzdb& db = get_tzdb();
const time_zone* tz = db.locate_zone("GMT");
VERIFY( tz != nullptr );
VERIFY( tz->name() == "Etc/GMT" );
VERIFY( tz == std::chrono::locate_zone("GMT") );
VERIFY( tz == db.locate_zone("Etc/GMT") );
VERIFY( tz == db.locate_zone("Etc/GMT+0") );
#ifdef HAVE_TZDB
VERIFY( db.locate_zone(db.current_zone()->name()) == db.current_zone() );
#endif
}
int main()
{
test_version();
test_current();
test_locate();
}

View file

@ -0,0 +1,76 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
// { dg-require-effective-target cxx11_abi }
// { dg-xfail-run-if "no weak override on AIX" { powerpc-ibm-aix* } }
#include <chrono>
#include <fstream>
#include <testsuite_hooks.h>
static bool override_used = true;
namespace __gnu_cxx
{
const char* zoneinfo_dir_override() {
override_used = true;
return "./";
}
}
void
test_load_leapseconds()
{
std::ofstream("leapseconds") << R"(
# These are all the real leap seconds as of 2022:
Leap 1972 Jun 30 23:59:60 + S
Leap 1972 Dec 31 23:59:60 + S
Leap 1973 Dec 31 23:59:60 + S
Leap 1974 Dec 31 23:59:60 + S
Leap 1975 Dec 31 23:59:60 + S
Leap 1976 Dec 31 23:59:60 + S
Leap 1977 Dec 31 23:59:60 + S
Leap 1978 Dec 31 23:59:60 + S
Leap 1979 Dec 31 23:59:60 + S
Leap 1981 Jun 30 23:59:60 + S
Leap 1982 Jun 30 23:59:60 + S
Leap 1983 Jun 30 23:59:60 + S
Leap 1985 Jun 30 23:59:60 + S
Leap 1987 Dec 31 23:59:60 + S
Leap 1989 Dec 31 23:59:60 + S
Leap 1990 Dec 31 23:59:60 + S
Leap 1992 Jun 30 23:59:60 + S
Leap 1993 Jun 30 23:59:60 + S
Leap 1994 Jun 30 23:59:60 + S
Leap 1995 Dec 31 23:59:60 + S
Leap 1997 Jun 30 23:59:60 + S
Leap 1998 Dec 31 23:59:60 + S
Leap 2005 Dec 31 23:59:60 + S
Leap 2008 Dec 31 23:59:60 + S
Leap 2012 Jun 30 23:59:60 + S
Leap 2015 Jun 30 23:59:60 + S
Leap 2016 Dec 31 23:59:60 + S
# These are fake leap seconds for testing purposes:
Leap 2093 Jun 30 23:59:59 - S
Leap 2093 Dec 31 23:59:60 + S
)";
const auto& db = std::chrono::get_tzdb();
VERIFY( override_used ); // If this fails then XFAIL for the target.
using namespace std::chrono;
// XXX update this value if the number of hardcoded leap seconds changes:
VERIFY( db.leap_seconds.size() == 29 );
auto i = db.leap_seconds.end() - 2;
VERIFY( i[0].date() == sys_days(2093y/July/1) - 1s );
VERIFY( i[0].value() == -1s );
VERIFY( i[1].date() == sys_days(2094y/January/1) );
VERIFY( i[1].value() == 1s );
}
int main()
{
test_load_leapseconds();
}

View file

@ -0,0 +1,123 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
// { dg-require-effective-target cxx11_abi }
// { dg-xfail-run-if "no weak override on AIX" { powerpc-ibm-aix* } }
#include <chrono>
#include <fstream>
#include <testsuite_hooks.h>
static bool override_used = true;
namespace __gnu_cxx
{
const char* zoneinfo_dir_override() {
override_used = true;
return "./";
}
}
std::string tzdata_zi = R"(
# version test1
# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
Rule Swiss 1941 1942 - May Mon>=1 1:00 1:00 S
Rule Swiss 1941 1942 - Oct Mon>=1 2:00 0 -
Rule EU 1977 1980 - Apr Sun>=1 1:00u 1:00 S
Rule EU 1977 only - Sep lastSun 1:00u 0 -
Rule EU 1978 only - Oct 1 1:00u 0 -
Rule EU 1979 1995 - Sep lastSun 1:00u 0 -
Rule EU 1981 max - Mar lastSun 1:00u 1:00 S
Rule EU 1996 max - Oct lastSun 1:00u 0 -
# Zone NAME STDOFF RULES FORMAT [UNTIL]
Zone Europe/Zurich 0:34:08 - LMT 1853 Jul 16
0:29:45.50 - BMT 1894 Jun
1:00 Swiss CE%sT 1981
1:00 EU CE%sT
Link Europe/Zurich Europe/Vaduz
)";
using namespace std::chrono;
void
test_access()
{
tzdb_list& list = get_tzdb_list();
tzdb_list::const_iterator first = list.begin();
tzdb_list::const_iterator last = list.end();
VERIFY( list.cbegin() == first );
VERIFY( list.cend() == last );
VERIFY( first != last );
VERIFY( &*first == &get_tzdb() );
VERIFY( &*first == &list.front() );
VERIFY( std::next(first) == last );
first++;
VERIFY( first == last );
}
void
test_reload()
{
tzdb_list& list = get_tzdb_list();
tzdb_list::const_iterator test1 = list.begin();
reload_tzdb();
VERIFY( list.begin() == test1 );
VERIFY( std::distance(list.begin(), list.end()) == 1 );
std::string new_tzdata_zi = tzdata_zi;
auto pos = new_tzdata_zi.find("test");
new_tzdata_zi[pos + 4] = '2';
std::ofstream("tzdata.zi") << new_tzdata_zi;
VERIFY( remote_version() == "test2" );
// List doesn't reload until requested to.
VERIFY( get_tzdb_list().begin() == test1 );
VERIFY( &get_tzdb() == &*test1 );
reload_tzdb();
VERIFY( list.begin() != test1 );
VERIFY( std::distance(list.begin(), list.end()) == 2 );
VERIFY( test1 == std::next(list.begin()) );
VERIFY( &get_tzdb() == &*list.begin() );
VERIFY( list.begin()->version == "test2" );
VERIFY( test1->version == "test1" );
}
void
test_erase()
{
tzdb_list& list = get_tzdb_list();
const int count = std::distance(list.begin(), list.end());
tzdb_list::const_iterator test2 = list.begin();
std::string new_tzdata_zi = tzdata_zi;
auto pos = new_tzdata_zi.find("test");
new_tzdata_zi[pos + 4] = '3';
std::ofstream("tzdata.zi") << new_tzdata_zi;
reload_tzdb();
VERIFY( std::distance(list.begin(), list.end()) == count + 1 );
VERIFY( list.begin()->version == "test3" );
list.erase_after(list.begin());
VERIFY( std::distance(list.begin(), list.end()) == count );
VERIFY( list.begin()->version == "test3" );
VERIFY( std::next(list.begin())->version == "test1" );
// As a GCC extension, the erased node is not destroyed
// while there are iterators referring to it.
VERIFY( test2->version == "test2" );
VERIFY( test2->leap_seconds == list.begin()->leap_seconds );
// But the iterator points to an unlinked list node now:
VERIFY( std::next(test2) == tzdb_list::const_iterator() );
}
int main()
{
std::ofstream("leapseconds") << '\n';
std::ofstream("tzdata.zi") << tzdata_zi;
test_access();
test_reload();
test_erase();
}

View file

@ -0,0 +1,20 @@
// { dg-options "-std=gnu++20" }
// { dg-do compile { target c++20 } }
// { dg-require-effective-target cxx11_abi }
#include <chrono>
using std::chrono::tzdb_list;
static_assert( ! std::is_default_constructible_v<tzdb_list> );
static_assert( ! std::is_copy_constructible_v<tzdb_list> );
static_assert( ! std::is_copy_assignable_v<tzdb_list> );
static_assert( ! std::is_move_constructible_v<tzdb_list> );
static_assert( ! std::is_move_assignable_v<tzdb_list> );
static_assert( std::is_destructible_v<tzdb_list> );
using IterTraits = std::iterator_traits<tzdb_list::const_iterator>;
static_assert( std::is_same_v<IterTraits::iterator_category,
std::forward_iterator_tag> );
static_assert( std::is_same_v<IterTraits::value_type, std::chrono::tzdb> );

View file

@ -0,0 +1,255 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
// { dg-require-effective-target tzdb }
// { dg-require-effective-target cxx11_abi }
#include <chrono>
#include <testsuite_hooks.h>
void
test_members()
{
using namespace std::chrono;
const time_zone* const zone = locate_zone("Europe/London");
sys_time<minutes> t = sys_days(2022y/February/1) + 1h + 23min;
zoned_time<minutes> zt("Europe/London", t);
VERIFY( zt.get_time_zone() == zone );
VERIFY( zt.get_sys_time() == t);
VERIFY( zt.get_local_time().time_since_epoch() == t.time_since_epoch() );
VERIFY( zt.get_info().offset == 0h );
VERIFY( zt.get_info().abbrev == "GMT" );
VERIFY( static_cast<sys_seconds>(zt) == t );
VERIFY( static_cast<local_seconds>(zt) == zt.get_local_time() );
t = sys_days(2022y/June/1);
zt = t;
VERIFY( zt.get_time_zone() == zone );
VERIFY( zt.get_sys_time() == t);
VERIFY( zt.get_local_time().time_since_epoch() == t.time_since_epoch() + 1h );
VERIFY( zt.get_info().offset == 1h );
VERIFY( zt.get_info().abbrev == "BST" );
VERIFY( static_cast<sys_seconds>(zt) == t );
VERIFY( static_cast<local_seconds>(zt) == zt.get_local_time() );
zoned_seconds zs(zt);
VERIFY( zs == zt );
local_time<seconds> local(zt.get_local_time() + days(1) + hours(2));
zt = time_point_cast<minutes>(local);
VERIFY( zt.get_sys_time() == zs.get_sys_time() + days(1) + hours(2) );
}
void
test_zurich()
{
using namespace std::chrono;
const time_zone* const zurich = locate_zone("Europe/Zurich");
{
sys_days d = 1853y/July/16;
auto z = zoned_seconds(zurich, sys_seconds(d) - 1s);
auto info = z.get_info();
VERIFY( info.offset == (34min + 8s) );
VERIFY( info.abbrev == "LMT" );
z = zoned_seconds(zurich, d);
info = z.get_info();
VERIFY( info.offset == (29min + 46s) );
VERIFY( info.abbrev == "BMT" );
z = zoned_seconds(zurich, d + 1s);
info = z.get_info();
VERIFY( info.offset == (29min + 46s) );
VERIFY( info.abbrev == "BMT" );
auto z2 = zoned_time(zurich, d + 0.001s);
info = z2.get_info();
VERIFY( info.offset == (29min + 46s) );
VERIFY( info.abbrev == "BMT" );
}
{
sys_days d = 1894y/June/1;
auto z = zoned_seconds(zurich, sys_seconds(d) - 1s);
auto info = z.get_info();
VERIFY( info.offset == (29min + 46s) );
VERIFY( info.abbrev == "BMT" );
z = zoned_seconds(zurich, d);
info = z.get_info();
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
}
{
sys_days d = 1941y/May/Monday[1];
auto z = zoned_seconds(zurich, d - 1s);
auto info = z.get_info();
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
// CEST daylight savings time starts at 1am local time (UTC+1).
z = zoned_seconds(zurich, d);
info = z.get_info();
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
}
{
sys_days d = 1941y/October/Monday[1];
auto z = zoned_seconds(zurich, d - 1s);
auto info = z.get_info();
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
// CET standard time starts at 2am local time (UTC+2).
z = zoned_seconds(zurich, d);
info = z.get_info();
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
}
{
sys_days d = 1942y/May/Monday[1];
auto z = zoned_seconds(zurich, d - 1s);
auto info = z.get_info();
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
// CEST daylight savings time starts at 1am local time (UTC+1).
z = zoned_seconds(zurich, d);
info = z.get_info();
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
}
{
sys_days d = 1942y/October/Monday[1];
auto z = zoned_seconds(zurich, d - 1s);
auto info = z.get_info();
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
// CET standard time starts at 2am local time (UTC+2).
z = zoned_seconds(zurich, d);
info = z.get_info();
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
}
{
sys_days d = 1943y/May/Monday[1];
// No daylight savings from 1943 until 1981.
auto z = zoned_seconds(zurich, d);
auto info = z.get_info();
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
z = zoned_seconds(zurich, d + days(60));
info = z.get_info();
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
z = zoned_seconds(zurich, d + years(10));
info = z.get_info();
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
z = zoned_seconds(zurich, sys_days(1979y/June/3));
info = z.get_info();
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
}
{
// Switzerland uses EU rules from 1981
sys_days d = 1981y/March/Sunday[last];
auto z = zoned_seconds(zurich, d);
auto info = z.get_info();
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
z = zoned_seconds(zurich, d + 59min + 59s);
info = z.get_info();
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
// CEST begins at 1am UTC
z = zoned_seconds(zurich, d + 1h);
info = z.get_info();
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
}
{
sys_days d = 1981y/September/Sunday[last];
auto z = zoned_seconds(zurich, d + 59min + 59s);
auto info = z.get_info();
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
// CEST ends at 1am UTC
z = zoned_seconds(zurich, d + 1h);
info = z.get_info();
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
}
{
sys_days d = 1994y/September/Sunday[last];
auto z = zoned_seconds(zurich, d + 59min + 59s);
auto info = z.get_info();
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
// CEST ends at 1am UTC
z = zoned_seconds(zurich, d + 1h);
info = z.get_info();
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
d = 1995y/September/Sunday[last];
z = zoned_seconds(zurich, d + 59min + 59s);
info = z.get_info();
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
// CEST ends at 1am UTC
z = zoned_seconds(zurich, d + 1h);
info = z.get_info();
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
d = 1996y/September/Sunday[last];
// CEST ends in October since 1996
z = zoned_seconds(zurich, d + 1h);
info = z.get_info();
VERIFY( info.offset == 2h );
VERIFY( info.abbrev == "CEST" );
d = 1996y/October/Sunday[last];
// CEST ends at 1am UTC
z = zoned_seconds(zurich, d + 1h);
info = z.get_info();
VERIFY( info.offset == 1h );
VERIFY( info.abbrev == "CET" );
}
}
int main()
{
test_members();
test_zurich();
}

View file

@ -0,0 +1,75 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
// { dg-require-effective-target tzdb }
#include <chrono>
#include <set>
#include <stdexcept>
#include <testsuite_hooks.h>
struct local_tz
{
local_tz() : name(std::chrono::current_zone()->name()) { }
explicit local_tz(std::string_view name) : name(name) { }
template<typename Dur>
std::chrono::sys_time<Dur> to_sys(const std::chrono::local_time<Dur>& d)
{ return std::chrono::locate_zone(name)->to_sys(d); }
template<typename Dur>
std::chrono::sys_time<Dur> to_local(const std::chrono::sys_time<Dur>& d)
{ return std::chrono::locate_zone(name)->to_sys(d); }
template<typename Dur>
std::chrono::sys_info get_info(const std::chrono::sys_time<Dur>& d)
{ return std::chrono::locate_zone(name)->get_info(d); }
struct indirect_cmp
{
bool operator()(const local_tz* lhs, const local_tz* rhs) const
{ return lhs->name < rhs->name; }
};
bool eq(const std::chrono::time_zone* tz) const noexcept
{ return name == tz->name(); }
private:
std::string_view name;
};
template<> struct std::chrono::zoned_traits<const local_tz*>
{
static const local_tz* default_zone()
{
return locate_zone(std::chrono::current_zone()->name());
}
static const local_tz* locate_zone(std::string_view name)
{
static std::set<const local_tz*, local_tz::indirect_cmp> zones;
local_tz tz(name);
if (auto z = zones.find(&tz); z != zones.end())
return *z;
if (std::chrono::locate_zone(name))
return *zones.insert(new local_tz(tz)).first;
throw std::runtime_error("zone not found");
}
};
void
test_custom_tzptr()
{
using namespace std::chrono;
zoned_time<seconds, const local_tz*> z;
VERIFY( z.get_time_zone()->eq(std::chrono::current_zone()) );
zoned_time<seconds, const local_tz*> z2(std::string_view("Europe/London"));
VERIFY( z2.get_time_zone()->eq(std::chrono::locate_zone("Europe/London")) );
}
int main()
{
test_custom_tzptr();
}

View file

@ -0,0 +1,79 @@
// { dg-options "-std=gnu++20" }
// { dg-do compile { target c++20 } }
// { dg-require-effective-target cxx11_abi }
#include <chrono>
using namespace std::chrono;
struct local_tz : time_zone { local_tz(); };
template<> struct std::chrono::zoned_traits<const local_tz*>
{
static auto default_zone() { return current_zone(); }
static auto locate_zone(std::string_view name)
{ return std::chrono::locate_zone(name); }
};
void
test_ctad()
{
zoned_time z1;
static_assert( std::is_same_v<decltype(z1), zoned_time<seconds>> );
zoned_time z2 = z1;
static_assert( std::is_same_v<decltype(z2), decltype(z1)> );
zoned_time z3 = sys_time<milliseconds>();
static_assert( std::is_same_v<decltype(z3), zoned_time<milliseconds>> );
const local_tz ltz;
zoned_time z4(&ltz);
static_assert( std::is_same_v<decltype(z4),
zoned_time<seconds, const local_tz*>> );
zoned_time z5("GMT");
static_assert( std::is_same_v<decltype(z5), zoned_time<seconds>> );
zoned_time z6(&ltz, sys_time<minutes>());
static_assert( std::is_same_v<decltype(z6),
zoned_time<seconds, const local_tz*>> );
zoned_time z7(&ltz, sys_time<milliseconds>());
static_assert( std::is_same_v<decltype(z7),
zoned_time<milliseconds, const local_tz*>> );
zoned_time z8("GMT", sys_time<minutes>());
static_assert( std::is_same_v<decltype(z8), zoned_time<seconds>> );
zoned_time z9("GMT", sys_time<microseconds>());
static_assert( std::is_same_v<decltype(z9), zoned_time<microseconds>> );
zoned_time z10(&ltz, local_time<minutes>());
static_assert( std::is_same_v<decltype(z10),
zoned_time<seconds, const local_tz*>> );
zoned_time z11(&ltz, local_time<nanoseconds>(), choose::earliest);
static_assert( std::is_same_v<decltype(z11),
zoned_time<nanoseconds, const local_tz*>> );
zoned_time z12("GMT", local_time<minutes>());
static_assert( std::is_same_v<decltype(z12), zoned_time<seconds>> );
zoned_time z13("GMT", local_time<nanoseconds>(), choose::earliest);
static_assert( std::is_same_v<decltype(z13), zoned_time<nanoseconds>> );
zoned_time z14(&ltz, z13);
static_assert( std::is_same_v<decltype(z14),
zoned_time<nanoseconds, const local_tz*>> );
zoned_time z15(&ltz, z12, choose::earliest);
static_assert( std::is_same_v<decltype(z15),
zoned_time<seconds, const local_tz*>> );
zoned_time z16("GMT", z14);
static_assert( std::is_same_v<decltype(z16), zoned_time<nanoseconds>> );
zoned_time z17("GMT", z12, choose::earliest);
static_assert( std::is_same_v<decltype(z17), zoned_time<seconds>> );
}

View file

@ -0,0 +1,9 @@
// { dg-options "-std=gnu++20" }
// { dg-do compile { target c++20 } }
// { dg-require-effective-target cxx11_abi }
#include <chrono>
std::chrono::zoned_time<std::chrono::year> z; // { dg-error "here" }
// { dg-error "static assertion failed" "" { target *-*-* } 0 }
// { dg-prune-output "common_type" }

View file

@ -0,0 +1,27 @@
// { dg-options "-std=gnu++20" }
// { dg-do compile { target c++20 } }
// { dg-require-effective-target cxx11_abi }
#include <chrono>
using namespace std::chrono;
static_assert( std::is_default_constructible_v<zoned_time<seconds>> );
static_assert( std::is_copy_constructible_v<zoned_time<seconds>> );
static_assert( std::is_copy_assignable_v<zoned_time<seconds>> );
static_assert( std::is_move_constructible_v<zoned_time<seconds>> );
static_assert( std::is_move_assignable_v<zoned_time<seconds>> );
static_assert( std::is_destructible_v<zoned_time<seconds>> );
static_assert( std::is_same_v<zoned_time<seconds>::duration, seconds> );
static_assert( std::is_same_v<zoned_time<nanoseconds>::duration, nanoseconds> );
static_assert( std::is_same_v<zoned_time<minutes>::duration, seconds> );
extern zoned_time<minutes> z;
static_assert( std::is_same_v<decltype(z == z), bool> );
// requires zoned_traits<time_zone*>::default_zone().
static_assert( ! std::is_default_constructible_v<zoned_time<seconds, time_zone*>> );
// requires zoned_traits<time_zone*>::locate_zone(string_view).
static_assert( ! std::is_constructible_v<zoned_time<seconds, time_zone*>,
std::string_view> );

View file

@ -0,0 +1,39 @@
// { dg-options "-std=gnu++20" }
// { dg-do run { target c++20 } }
// { dg-require-effective-target cxx11_abi }
#include <chrono>
#include <testsuite_hooks.h>
using namespace std::chrono;
static_assert( std::is_empty_v<zoned_traits<const time_zone*>> );
static_assert(std::is_default_constructible_v<zoned_traits<const time_zone*>>);
// The primary template is a complete type, it just has no members.
static_assert( std::is_empty_v<zoned_traits<time_zone*>> );
static_assert(std::is_default_constructible_v<zoned_traits<time_zone*>>);
static_assert( std::is_empty_v<zoned_traits<int>> );
static_assert(std::is_default_constructible_v<zoned_traits<int>>);
void
test_default_zone()
{
auto p = zoned_traits<const time_zone*>::default_zone();
static_assert( std::is_same_v<decltype(p), const time_zone*> );
VERIFY( p == locate_zone("UTC") );
}
void
test_locate_zone()
{
auto p = zoned_traits<const time_zone*>::locate_zone("GMT");
static_assert( std::is_same_v<decltype(p), const time_zone*> );
VERIFY( p == locate_zone("GMT") );
}
int main()
{
test_default_zone();
test_locate_zone();
}