<div dir="ltr"><br><br><div class="gmail_quote"><div dir="ltr">On Fri, Aug 10, 2018 at 2:43 PM Howard Hinnant via cfe-dev <<a href="mailto:cfe-dev@lists.llvm.org">cfe-dev@lists.llvm.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">On Aug 10, 2018, at 1:28 PM, Marshall Clow via cfe-dev <<a href="mailto:cfe-dev@lists.llvm.org" target="_blank">cfe-dev@lists.llvm.org</a>> wrote:<br>
> <br>
> * The clock stuff being added in C++20 has already been discussed here.<br>
<br>
I’ve missed the discussions on file_time_type, however I thought I should throw in my opinion here before it is too late to do anything about it.<br>
<br>
I believe it is a mistake to model file_time_type with 128 bits. It would be acceptable if this was absolutely necessary to get the job done, but it isn’t. The 16 byte integer is unnecessarily expensive to get the job done.<br>
<br>
file_time_type does not need to model the full range and precision of timespec (which on 64 bit platforms is a 128 bit type). All file_time_type needs to model is the full range and precision of what the underlying file system libraries are capable of producing.<br>
<br>
The latest Linux file system is ext4 (<a href="https://en.wikipedia.org/wiki/Ext4" rel="noreferrer" target="_blank">https://en.wikipedia.org/wiki/Ext4</a>) and is capable of nanosecond resolution. However its timestamp is only 64 bits. It has a range of approximately [1901-12-14, 2446-05-10]. Modeling ext4 would be a good design decision for libc++. libc++ could also model other file systems (Windows, macOS). All of these are based on 64 bit timestamps.<br></blockquote><div><br></div><div>It does seem that I overlooked the fact that EXT4 only provides 64 bits of precision despite timespec providing more.</div><div><br></div><div>I'll look into other filesystems to see if any offer more than that. If not, perhaps I was mistaken trying to match the precision and range of timespec.</div><div><br></div><div>Part of me is still concerned with the future, and the filesystems which are yet to exist.</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
Here is a file_clock, quickly thrown together, lightly tested, that models ext4:<br>
<br>
#include "date/tz.h"<br>
#include <ostream><br>
#include <istream><br>
<br>
namespace filesystem<br>
{<br>
<br>
struct file_clock<br>
{<br>
using duration = std::chrono::nanoseconds;<br>
using rep = duration::rep;<br>
using period = duration::period;<br>
using time_point = std::chrono::time_point<file_clock>;<br>
static constexpr bool is_steady = false;<br>
<br>
static time_point now();<br>
<br>
template<typename Duration><br>
static<br>
std::chrono::time_point<std::chrono::system_clock, Duration><br>
to_sys(const std::chrono::time_point<file_clock, Duration>& t) noexcept;<br>
<br>
template<typename Duration><br>
static<br>
std::chrono::time_point<file_clock, Duration><br>
from_sys(const std::chrono::time_point<std::chrono::system_clock, Duration>& t) noexcept;<br>
<br>
template<typename Duration><br>
static<br>
std::chrono::time_point<date::local_t, Duration><br>
to_local(const std::chrono::time_point<file_clock, Duration>& t) noexcept;<br>
<br>
template<typename Duration><br>
static<br>
std::chrono::time_point<file_clock, Duration><br>
from_local(const std::chrono::time_point<date::local_t, Duration>& t) noexcept;<br>
<br>
// private helpers<br>
<br>
static<br>
timespec<br>
to_timespec(const time_point& t) noexcept;<br>
<br>
static<br>
time_point<br>
from_timespec(const timespec& t) noexcept;<br>
};<br>
<br>
template <class Duration><br>
using file_time = std::chrono::time_point<file_clock, Duration>;<br>
<br>
using file_time_type = file_clock::time_point;<br>
<br>
template <class Duration><br>
inline<br>
std::chrono::time_point<std::chrono::system_clock, Duration><br>
file_clock::to_sys(const std::chrono::time_point<file_clock, Duration>& t) noexcept<br>
{<br>
using namespace date;<br>
return sys_time<Duration>{t.time_since_epoch()} +<br>
(sys_days{2174_y/1/1} - sys_days{1970_y/1/1});<br>
}<br>
<br>
template <class Duration><br>
inline<br>
std::chrono::time_point<file_clock, Duration><br>
file_clock::from_sys(const std::chrono::time_point<std::chrono::system_clock, Duration>& t) noexcept<br>
{<br>
using namespace date;<br>
return file_time<Duration>{t.time_since_epoch()} -<br>
(sys_days{2174_y/1/1} - sys_days{1970_y/1/1});<br>
}<br>
<br>
template <class Duration><br>
inline<br>
std::chrono::time_point<date::local_t, Duration><br>
file_clock::to_local(const std::chrono::time_point<file_clock, Duration>& t) noexcept<br>
{<br>
using namespace date;<br>
return local_time<Duration>{to_sys(t).time_since_epoch()};<br>
}<br>
<br>
template <class Duration><br>
inline<br>
std::chrono::time_point<file_clock, Duration><br>
file_clock::from_local(const std::chrono::time_point<date::local_t, Duration>& t) noexcept<br>
{<br>
using namespace date;<br>
return file_time<Duration>{from_sys(sys_time<Duration>{t.time_since_epoch()})};<br>
}<br>
<br>
file_clock::time_point<br>
file_clock::now()<br>
{<br>
return from_sys(std::chrono::system_clock::now());<br>
}<br>
<br>
template <class CharT, class Traits, class Duration><br>
std::basic_ostream<CharT, Traits>&<br>
to_stream(std::basic_ostream<CharT, Traits>& os, const CharT* fmt,<br>
const file_time<Duration>& t)<br>
{<br>
using namespace std::chrono;<br>
const std::string abbrev("UTC");<br>
constexpr std::chrono::seconds offset{0};<br>
using D128 = duration<__int128, typename Duration::period>;<br>
return date::to_stream(os, fmt, file_clock::to_local(time_point_cast<D128>(t)),<br>
&abbrev, &offset);<br>
}<br>
<br>
template <class Duration, class CharT, class Traits, class Alloc = std::allocator<CharT>><br>
std::basic_istream<CharT, Traits>&<br>
from_stream(std::basic_istream<CharT, Traits>& is, const CharT* fmt,<br>
file_time<Duration>& tp,<br>
std::basic_string<CharT, Traits, Alloc>* abbrev = nullptr,<br>
std::chrono::minutes* offset = nullptr)<br>
{<br>
using namespace date;<br>
using namespace std::chrono;<br>
using D128 = duration<__int128, typename Duration::period>;<br>
local_time<D128> lp;<br>
from_stream(is, fmt, lp, abbrev, offset);<br>
if (!is.fail())<br>
tp = file_clock::from_local(lp);<br>
return is;<br>
}<br>
<br>
template <class CharT, class Traits, class Duration><br>
std::basic_ostream<CharT, Traits>&<br>
operator<<(std::basic_ostream<CharT, Traits>& os, const file_time<Duration>& t)<br>
{<br>
const CharT fmt[] = {'%', 'F', ' ', '%', 'T', CharT{}};<br>
return to_stream(os, fmt, t);<br>
}<br>
<br>
inline<br>
timespec<br>
file_clock::to_timespec(const time_point& t) noexcept<br>
{<br>
using namespace date;<br>
using namespace std::chrono;<br>
auto tp = to_sys(time_point_cast<std::chrono::duration<__int128, std::nano>>(t));<br>
auto s = floor<seconds>(tp);<br>
timespec ts;<br>
ts.tv_sec = static_cast<decltype(ts.tv_sec)>(s.time_since_epoch().count());<br>
ts.tv_nsec = static_cast<decltype(ts.tv_nsec)>((tp - s).count());<br>
return ts;<br>
}<br>
<br>
inline<br>
file_clock::time_point<br>
file_clock::from_timespec(const timespec& t) noexcept<br>
{<br>
using namespace date;<br>
using namespace std::chrono;<br>
auto d = std::chrono::duration<__int128>{t.tv_sec} + nanoseconds{t.tv_nsec};<br>
return time_point_cast<duration>(from_sys(sys_time<decltype(d)>{d}));<br>
}<br>
<br>
} // namespace filesystem<br>
<br>
#include <iostream><br>
#include <sstream><br>
<br>
int<br>
main()<br>
{<br>
using namespace std;<br>
using namespace date;<br>
std::cout << filesystem::file_clock::time_point::min() << '\n';<br>
std::cout << filesystem::file_clock::now() << '\n';<br>
std::cout << filesystem::file_clock::time_point::max() << '\n';<br>
std::istringstream in{"2466-04-11 23:47:16.854775807"};<br>
filesystem::file_clock::time_point tp;<br>
in >> date::parse("%F %T", tp);<br>
cout << tp << '\n';<br>
in.clear();<br>
in.str("1881-09-22 00:12:43.145224192");<br>
in >> date::parse("%F %T", tp);<br>
cout << tp << '\n';<br>
timespec ts = {15661036036, 854775807}; // or {-2785708037, 145224192}<br>
tp = filesystem::file_clock::from_timespec(ts);<br>
cout << tp << '\n';<br>
ts = filesystem::file_clock::to_timespec(tp);<br>
cout << "{" << ts.tv_sec << ", " << ts.tv_nsec << "}\n";<br>
using s32 = chrono::duration<int>;<br>
using ns64 = chrono::duration<long, nano>;<br>
using uns64 = chrono::duration<unsigned long, nano>;<br>
using ns128 = chrono::duration<__int128, nano>;<br>
cout << date::sys_time<s32>::min() + ns128{uns64::max()} << '\n';<br>
cout << date::sys_time<s32>::min() + ns128{ns64::max()} + 1ns << '\n';<br>
}<br>
<br>
It is a 64bit timestamp with nanosecond resolution that is capable of representing a superset of ext4 (about +/- 20 years on either side of the limits of ext4). It _does_ internally use __int128 for a few intermediate computations such as converting to/from a timespec. This allows it avoid overflow out near min()/max(). However, the most common operations users will encounter are simply the arithmetic involving time_point and duration, and these are strictly 64 bit (and already provided by chrono).<br>
<br>
This is what I advise for the libc++ filesystem library. I have also sent this code to the gcc developers. Consider it public domain.<br>
<br>
Howard<br>
<br>
_______________________________________________<br>
cfe-dev mailing list<br>
<a href="mailto:cfe-dev@lists.llvm.org" target="_blank">cfe-dev@lists.llvm.org</a><br>
<a href="http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev" rel="noreferrer" target="_blank">http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev</a><br>
</blockquote></div></div>