1d981977dSEric Fiselier============== 2d981977dSEric FiselierFile Time Type 3d981977dSEric Fiselier============== 4d981977dSEric Fiselier 5d981977dSEric Fiselier.. contents:: 6d981977dSEric Fiselier :local: 7d981977dSEric Fiselier 8d981977dSEric Fiselier.. _file-time-type-motivation: 9d981977dSEric Fiselier 10d981977dSEric FiselierMotivation 11d981977dSEric Fiselier========== 12d981977dSEric Fiselier 13d981977dSEric FiselierThe filesystem library provides interfaces for getting and setting the last 14d981977dSEric Fiselierwrite time of a file or directory. The interfaces use the ``file_time_type`` 15d981977dSEric Fiseliertype, which is a specialization of ``chrono::time_point`` for the 16d981977dSEric Fiselier"filesystem clock". According to [fs.filesystem.syn] 17d981977dSEric Fiselier 18d981977dSEric Fiselier trivial-clock is an implementation-defined type that satisfies the 19d981977dSEric Fiselier Cpp17TrivialClock requirements ([time.clock.req]) and that is capable of 20d981977dSEric Fiselier representing and measuring file time values. Implementations should ensure 212218f599Szoecarver that the resolution and range of file_time_type reflect the operating 22d981977dSEric Fiselier system dependent resolution and range of file time values. 23d981977dSEric Fiselier 24d981977dSEric Fiselier 25d981977dSEric FiselierOn POSIX systems, file times are represented using the ``timespec`` struct, 26d981977dSEric Fiselierwhich is defined as follows: 27d981977dSEric Fiselier 28d981977dSEric Fiselier.. code-block:: cpp 29d981977dSEric Fiselier 30d981977dSEric Fiselier struct timespec { 31d981977dSEric Fiselier time_t tv_sec; 32d981977dSEric Fiselier long tv_nsec; 33d981977dSEric Fiselier }; 34d981977dSEric Fiselier 35d981977dSEric FiselierTo represent the range and resolution of ``timespec``, we need to (A) have 36d981977dSEric Fiseliernanosecond resolution, and (B) use more than 64 bits (assuming a 64 bit ``time_t``). 37d981977dSEric Fiselier 38d981977dSEric FiselierAs the standard requires us to use the ``chrono`` interface, we have to define 39d981977dSEric Fiselierour own filesystem clock which specifies the period and representation of 40d981977dSEric Fiselierthe time points and duration it provides. It will look like this: 41d981977dSEric Fiselier 42d981977dSEric Fiselier.. code-block:: cpp 43d981977dSEric Fiselier 44d981977dSEric Fiselier struct _FilesystemClock { 45d981977dSEric Fiselier using period = nano; 46d981977dSEric Fiselier using rep = TBD; // What is this? 47d981977dSEric Fiselier 48d981977dSEric Fiselier using duration = chrono::duration<rep, period>; 49d981977dSEric Fiselier using time_point = chrono::time_point<_FilesystemClock>; 50d981977dSEric Fiselier 51d981977dSEric Fiselier // ... // 52d981977dSEric Fiselier }; 53d981977dSEric Fiselier 54d981977dSEric Fiselier using file_time_type = _FilesystemClock::time_point; 55d981977dSEric Fiselier 56d981977dSEric Fiselier 57d981977dSEric FiselierTo get nanosecond resolution, we simply define ``period`` to be ``std::nano``. 58d981977dSEric FiselierBut what type can we use as the arithmetic representation that is capable 59d981977dSEric Fiselierof representing the range of the ``timespec`` struct? 60d981977dSEric Fiselier 61d981977dSEric FiselierProblems To Consider 62d981977dSEric Fiselier==================== 63d981977dSEric Fiselier 64f636411aSLouis DionneBefore considering solutions, let's consider the problems they should solve, 65d981977dSEric Fiselierand how important solving those problems are: 66d981977dSEric Fiselier 67d981977dSEric Fiselier 68d981977dSEric FiselierHaving a Smaller Range than ``timespec`` 69d981977dSEric Fiselier---------------------------------------- 70d981977dSEric Fiselier 71d981977dSEric FiselierOne solution to the range problem is to simply reduce the resolution of 72d981977dSEric Fiselier``file_time_type`` to be less than that of nanoseconds. This is what libc++'s 73d981977dSEric Fiselierinitial implementation of ``file_time_type`` did; it's also what 74d981977dSEric Fiselier``std::system_clock`` does. As a result, it can represent time points about 75d981977dSEric Fiselier292 thousand years on either side of the epoch, as opposed to only 292 years 76d981977dSEric Fiselierat nanosecond resolution. 77d981977dSEric Fiselier 78d981977dSEric Fiselier``timespec`` can represent time points +/- 292 billion years from the epoch 79d981977dSEric Fiselier(just in case you needed a time point 200 billion years before the big bang, 80d981977dSEric Fiselierand with nanosecond resolution). 81d981977dSEric Fiselier 82d981977dSEric FiselierTo get the same range, we would need to drop our resolution to that of seconds 83d981977dSEric Fiselierto come close to having the same range. 84d981977dSEric Fiselier 85d981977dSEric FiselierThis begs the question, is the range problem "really a problem"? Sane usages 86f0b379c9SEric Fiselierof file time stamps shouldn't exceed +/- 300 years, so should we care to support it? 87d981977dSEric Fiselier 88d981977dSEric FiselierI believe the answer is yes. We're not designing the filesystem time API, we're 89d981977dSEric Fiselierproviding glorified C++ wrappers for it. If the underlying API supports 90d981977dSEric Fiseliera value, then we should too. Our wrappers should not place artificial restrictions 91d981977dSEric Fiselieron users that are not present in the underlying filesystem. 92d981977dSEric Fiselier 93f0b379c9SEric FiselierHaving a smaller range that the underlying filesystem forces the 94d981977dSEric Fiselierimplementation to report ``value_too_large`` errors when it encounters a time 95d981977dSEric Fiselierpoint that it can't represent. This can cause the call to ``last_write_time`` 96d981977dSEric Fiselierto throw in cases where the user was confident the call should succeed. (See below) 97d981977dSEric Fiselier 98d981977dSEric Fiselier 99d981977dSEric Fiselier.. code-block:: cpp 100d981977dSEric Fiselier 101d981977dSEric Fiselier #include <filesystem> 102d981977dSEric Fiselier using namespace std::filesystem; 103d981977dSEric Fiselier 104d981977dSEric Fiselier // Set the times using the system interface. 105d981977dSEric Fiselier void set_file_times(const char* path, struct timespec ts) { 106d981977dSEric Fiselier timespec both_times[2]; 107d981977dSEric Fiselier both_times[0] = ts; 108d981977dSEric Fiselier both_times[1] = ts; 109d981977dSEric Fiselier int result = ::utimensat(AT_FDCWD, path, both_times, 0); 110d981977dSEric Fiselier assert(result != -1); 111d981977dSEric Fiselier } 112d981977dSEric Fiselier 113d981977dSEric Fiselier // Called elsewhere to set the file time to something insane, and way 114d981977dSEric Fiselier // out of the 300 year range we might expect. 115d981977dSEric Fiselier void some_bad_persons_code() { 116d981977dSEric Fiselier struct timespec new_times; 117d981977dSEric Fiselier new_times.tv_sec = numeric_limits<time_t>::max(); 118d981977dSEric Fiselier new_times.tv_nsec = 0; 119d981977dSEric Fiselier set_file_times("/tmp/foo", new_times); // OK, supported by most FSes 120d981977dSEric Fiselier } 121d981977dSEric Fiselier 1222df59c50SJF Bastien int main(int, char**) { 123d981977dSEric Fiselier path p = "/tmp/foo"; 124d981977dSEric Fiselier file_status st = status(p); 125d981977dSEric Fiselier if (!exists(st) || !is_regular_file(st)) 126d981977dSEric Fiselier return 1; 127d981977dSEric Fiselier if ((st.permissions() & perms::others_read) == perms::none) 128d981977dSEric Fiselier return 1; 129d981977dSEric Fiselier // It seems reasonable to assume this call should succeed. 130d981977dSEric Fiselier file_time_type tp = last_write_time(p); // BAD! Throws value_too_large. 1312df59c50SJF Bastien return 0; 132d981977dSEric Fiselier } 133d981977dSEric Fiselier 134d981977dSEric Fiselier 135d981977dSEric FiselierHaving a Smaller Resolution than ``timespec`` 136d981977dSEric Fiselier--------------------------------------------- 137d981977dSEric Fiselier 138d981977dSEric FiselierAs mentioned in the previous section, one way to solve the range problem 139f0b379c9SEric Fiselieris by reducing the resolution. But matching the range of ``timespec`` using a 140d981977dSEric Fiselier64 bit representation requires limiting the resolution to seconds. 141d981977dSEric Fiselier 142d981977dSEric FiselierSo we might ask: Do users "need" nanosecond precision? Is seconds not good enough? 143d981977dSEric FiselierI limit my consideration of the point to this: Why was it not good enough for 144d981977dSEric Fiselierthe underlying system interfaces? If it wasn't good enough for them, then it 145d981977dSEric Fiselierisn't good enough for us. Our job is to match the filesystems range and 146d981977dSEric Fiselierrepresentation, not design it. 147d981977dSEric Fiselier 148d981977dSEric Fiselier 149d981977dSEric FiselierHaving a Larger Range than ``timespec`` 150d981977dSEric Fiselier---------------------------------------- 151d981977dSEric Fiselier 152f0b379c9SEric FiselierWe should also consider the opposite problem of having a ``file_time_type`` 153f0b379c9SEric Fiselierthat is able to represent a larger range than ``timespec``. At least in 154d981977dSEric Fiselierthis case ``last_write_time`` can be used to get and set all possible values 155d981977dSEric Fiseliersupported by the underlying filesystem; meaning ``last_write_time(p)`` will 156*11e29758SKazu Hiratanever throw an overflow error when retrieving a value. 157d981977dSEric Fiselier 158f0b379c9SEric FiselierHowever, this introduces a new problem, where users are allowed to attempt to 159f0b379c9SEric Fiseliercreate a time point beyond what the filesystem can represent. Two particular 160f0b379c9SEric Fiseliervalues which cause this are ``file_time_type::min()`` and 161f0b379c9SEric Fiselier``file_time_type::max()``. As a result, the following code would throw: 162d981977dSEric Fiselier 163d981977dSEric Fiselier.. code-block:: cpp 164d981977dSEric Fiselier 165d981977dSEric Fiselier void test() { 166d981977dSEric Fiselier last_write_time("/tmp/foo", file_time_type::max()); // Throws 167d981977dSEric Fiselier last_write_time("/tmp/foo", file_time_type::min()); // Throws. 168d981977dSEric Fiselier } 169d981977dSEric Fiselier 170d981977dSEric FiselierApart from cases explicitly using ``min`` and ``max``, I don't see users taking 171f0b379c9SEric Fiseliera valid time point, adding a couple hundred billions of years in error, 172f0b379c9SEric Fiselierand then trying to update a file's write time to that value very often. 173d981977dSEric Fiselier 174d981977dSEric FiselierCompared to having a smaller range, this problem seems preferable. At least 175d981977dSEric Fiseliernow we can represent any time point the filesystem can, so users won't be forced 176d981977dSEric Fiselierto revert back to system interfaces to avoid limitations in the C++ STL. 177d981977dSEric Fiselier 178d981977dSEric FiselierI posit that we should only consider this concern *after* we have something 179d981977dSEric Fiselierwith at least the same range and resolution of the underlying filesystem. The 180d981977dSEric Fiselierlatter two problems are much more important to solve. 181d981977dSEric Fiselier 182d981977dSEric FiselierPotential Solutions And Their Complications 183d981977dSEric Fiselier=========================================== 184d981977dSEric Fiselier 185d981977dSEric FiselierSource Code Portability Across Implementations 186d981977dSEric Fiselier----------------------------------------------- 187d981977dSEric Fiselier 188d981977dSEric FiselierAs we've discussed, ``file_time_type`` needs a representation that uses more 189d981977dSEric Fiselierthan 64 bits. The possible solutions include using ``__int128_t``, emulating a 190d981977dSEric Fiselier128 bit integer using a class, or potentially defining a ``timespec`` like 191f636411aSLouis Dionnearithmetic type. All three will allow us to, at minimum, match the range 192d981977dSEric Fiselierand resolution, and the last one might even allow us to match them exactly. 193d981977dSEric Fiselier 194f0b379c9SEric FiselierBut when considering these potential solutions we need to consider more than 195f0b379c9SEric Fiselierjust the values they can represent. We need to consider the effects they will 196f0b379c9SEric Fiselierhave on users and their code. For example, each of them breaks the following 197f0b379c9SEric Fiseliercode in some way: 198d981977dSEric Fiselier 199d981977dSEric Fiselier.. code-block:: cpp 200d981977dSEric Fiselier 201d981977dSEric Fiselier // Bug caused by an unexpected 'rep' type returned by count. 202d981977dSEric Fiselier void print_time(path p) { 203d981977dSEric Fiselier // __int128_t doesn't have streaming operators, and neither would our 204d981977dSEric Fiselier // custom arithmetic types. 205d981977dSEric Fiselier cout << last_write_time(p).time_since_epoch().count() << endl; 206d981977dSEric Fiselier } 207d981977dSEric Fiselier 208d981977dSEric Fiselier // Overflow during creation bug. 209d981977dSEric Fiselier file_time_type timespec_to_file_time_type(struct timespec ts) { 210d981977dSEric Fiselier // woops! chrono::seconds and chrono::nanoseconds use a 64 bit representation 211d981977dSEric Fiselier // this may overflow before it's converted to a file_time_type. 212d981977dSEric Fiselier auto dur = seconds(ts.tv_sec) + nanoseconds(ts.tv_nsec); 213d981977dSEric Fiselier return file_time_type(dur); 214d981977dSEric Fiselier } 215d981977dSEric Fiselier 216d981977dSEric Fiselier file_time_type correct_timespec_to_file_time_type(struct timespec ts) { 217d981977dSEric Fiselier // This is the correct version of the above example, where we 218f636411aSLouis Dionne // avoid using the chrono typedefs as they're not sufficient. 219d981977dSEric Fiselier // Can we expect users to avoid this bug? 220d981977dSEric Fiselier using fs_seconds = chrono::duration<file_time_type::rep>; 221d981977dSEric Fiselier using fs_nanoseconds = chrono::duration<file_time_type::rep, nano>; 222d981977dSEric Fiselier auto dur = fs_seconds(ts.tv_sec) + fs_nanoseconds(tv.tv_nsec); 223d981977dSEric Fiselier return file_time_type(dur); 224d981977dSEric Fiselier } 225d981977dSEric Fiselier 226d981977dSEric Fiselier // Implicit truncation during conversion bug. 227d981977dSEric Fiselier intmax_t get_time_in_seconds(path p) { 228b54393e6SEric Fiselier using fs_seconds = duration<file_time_type::rep, ratio<1, 1> >; 229d981977dSEric Fiselier auto tp = last_write_time(p); 230d981977dSEric Fiselier 231d981977dSEric Fiselier // This works with truncation for __int128_t, but what does it do for 232d981977dSEric Fiselier // our custom arithmetic types. 233d981977dSEric Fiselier return duration_cast<fs_seconds>().count(); 234d981977dSEric Fiselier } 235d981977dSEric Fiselier 236d981977dSEric Fiselier 237d981977dSEric FiselierEach of the above examples would require a user to adjust their filesystem code 238f0b379c9SEric Fiselierto the particular eccentricities of the representation, hopefully only in such 239f0b379c9SEric Fiseliera way that the code is still portable across implementations. 240d981977dSEric Fiselier 241f0b379c9SEric FiselierAt least some of the above issues are unavoidable, no matter what 242f0b379c9SEric Fiselierrepresentation we choose. But some representations may be quirkier than others, 243f0b379c9SEric Fiselierand, as I'll argue later, using an actual arithmetic type (``__int128_t``) 244d981977dSEric Fiselierprovides the least aberrant behavior. 245d981977dSEric Fiselier 246d981977dSEric Fiselier 247d981977dSEric FiselierChrono and ``timespec`` Emulation. 248d981977dSEric Fiselier---------------------------------- 249d981977dSEric Fiselier 250d981977dSEric FiselierOne of the options we've considered is using something akin to ``timespec`` 251d981977dSEric Fiselierto represent the ``file_time_type``. It only seems natural seeing as that's 252d981977dSEric Fiselierwhat the underlying system uses, and because it might allow us to match 253d981977dSEric Fiselierthe range and resolution exactly. But would it work with chrono? And could 254d981977dSEric Fiselierit still act at all like a ``timespec`` struct? 255d981977dSEric Fiselier 256f636411aSLouis DionneFor ease of consideration, let's consider what the implementation might 257d981977dSEric Fiselierlook like. 258d981977dSEric Fiselier 259d981977dSEric Fiselier.. code-block:: cpp 260d981977dSEric Fiselier 261d981977dSEric Fiselier struct fs_timespec_rep { 262d981977dSEric Fiselier fs_timespec_rep(long long v) 263d981977dSEric Fiselier : tv_sec(v / nano::den), tv_nsec(v % nano::den) 264d981977dSEric Fiselier { } 265d981977dSEric Fiselier private: 266d981977dSEric Fiselier time_t tv_sec; 267d981977dSEric Fiselier long tv_nsec; 268d981977dSEric Fiselier }; 269d981977dSEric Fiselier bool operator==(fs_timespec_rep, fs_timespec_rep); 270d981977dSEric Fiselier fs_int128_rep operator+(fs_timespec_rep, fs_timespec_rep); 271d981977dSEric Fiselier // ... arithmetic operators ... // 272d981977dSEric Fiselier 273d981977dSEric FiselierThe first thing to notice is that we can't construct ``fs_timespec_rep`` like 274f0b379c9SEric Fiseliera ``timespec`` by passing ``{secs, nsecs}``. Instead we're limited to 275f0b379c9SEric Fiselierconstructing it from a single 64 bit integer. 276d981977dSEric Fiselier 277d981977dSEric FiselierWe also can't allow the user to inspect the ``tv_sec`` or ``tv_nsec`` values 278d981977dSEric Fiselierdirectly. A ``chrono::duration`` represents its value as a tick period and a 279d981977dSEric Fiseliernumber of ticks stored using ``rep``. The representation is unaware of the 280f0b379c9SEric Fiseliertick period it is being used to represent, but ``timespec`` is setup to assume 281f0b379c9SEric Fiseliera nanosecond tick period; which is the only case where the names ``tv_sec`` 282f636411aSLouis Dionneand ``tv_nsec`` match the values they store. 283d981977dSEric Fiselier 284f636411aSLouis DionneWhen we convert a nanosecond duration to seconds, ``fs_timespec_rep`` will 285f0b379c9SEric Fiselieruse ``tv_sec`` to represent the number of giga seconds, and ``tv_nsec`` the 286f636411aSLouis Dionneremaining seconds. Let's consider how this might cause a bug were users allowed 287d981977dSEric Fiselierto manipulate the fields directly. 288d981977dSEric Fiselier 289d981977dSEric Fiselier.. code-block:: cpp 290d981977dSEric Fiselier 291d981977dSEric Fiselier template <class Period> 292d981977dSEric Fiselier timespec convert_to_timespec(duration<fs_time_rep, Period> dur) { 293d981977dSEric Fiselier fs_timespec_rep rep = dur.count(); 294d981977dSEric Fiselier return {rep.tv_sec, rep.tv_nsec}; // Oops! Period may not be nanoseconds. 295d981977dSEric Fiselier } 296d981977dSEric Fiselier 297d981977dSEric Fiselier template <class Duration> 298d981977dSEric Fiselier Duration convert_to_duration(timespec ts) { 299d981977dSEric Fiselier Duration dur({ts.tv_sec, ts.tv_nsec}); // Oops! Period may not be nanoseconds. 300d981977dSEric Fiselier return file_time_type(dur); 301d981977dSEric Fiselier file_time_type tp = last_write_time(p); 302d981977dSEric Fiselier auto dur = 303d981977dSEric Fiselier } 304d981977dSEric Fiselier 305d981977dSEric Fiselier time_t extract_seconds(file_time_type tp) { 306d981977dSEric Fiselier // Converting to seconds is a silly bug, but I could see it happening. 307d981977dSEric Fiselier using SecsT = chrono::duration<file_time_type::rep, ratio<1, 1>>; 308d981977dSEric Fiselier auto secs = duration_cast<Secs>(tp.time_since_epoch()); 309d981977dSEric Fiselier // tv_sec is now representing gigaseconds. 310d981977dSEric Fiselier return secs.count().tv_sec; // Oops! 311d981977dSEric Fiselier } 312d981977dSEric Fiselier 313f0b379c9SEric FiselierDespite ``fs_timespec_rep`` not being usable in any manner resembling 314d981977dSEric Fiselier``timespec``, it still might buy us our goal of matching its range exactly, 315d981977dSEric Fiselierright? 316d981977dSEric Fiselier 317d981977dSEric FiselierSort of. Chrono provides a specialization point which specifies the minimum 318d981977dSEric Fiselierand maximum values for a custom representation. It looks like this: 319d981977dSEric Fiselier 320d981977dSEric Fiselier.. code-block:: cpp 321d981977dSEric Fiselier 322d981977dSEric Fiselier template <> 323d981977dSEric Fiselier struct duration_values<fs_timespec_rep> { 324d981977dSEric Fiselier static fs_timespec_rep zero(); 325d981977dSEric Fiselier static fs_timespec_rep min(); 326d981977dSEric Fiselier static fs_timespec_rep max() { // assume friendship. 327d981977dSEric Fiselier fs_timespec_rep val; 328d981977dSEric Fiselier val.tv_sec = numeric_limits<time_t>::max(); 329d981977dSEric Fiselier val.tv_nsec = nano::den - 1; 330d981977dSEric Fiselier return val; 331d981977dSEric Fiselier } 332d981977dSEric Fiselier }; 333d981977dSEric Fiselier 334f0b379c9SEric FiselierNotice that ``duration_values`` doesn't tell the representation what tick 335f0b379c9SEric Fiselierperiod it's actually representing. This would indeed correctly limit the range 336f0b379c9SEric Fiselierof ``duration<fs_timespec_rep, nano>`` to exactly that of ``timespec``. But 337f0b379c9SEric Fiseliernanoseconds isn't the only tick period it will be used to represent. For 338f0b379c9SEric Fiselierexample: 339d981977dSEric Fiselier 340d981977dSEric Fiselier.. code-block:: cpp 341d981977dSEric Fiselier 342d981977dSEric Fiselier void test() { 343d981977dSEric Fiselier using rep = file_time_type::rep; 344d981977dSEric Fiselier using fs_nsec = duration<rep, nano>; 345d981977dSEric Fiselier using fs_sec = duration<rep>; 346d981977dSEric Fiselier fs_nsec nsecs(fs_seconds::max()); // Truncates 347d981977dSEric Fiselier } 348d981977dSEric Fiselier 349f0b379c9SEric FiselierThough the above example may appear silly, I think it follows from the incorrect 350d981977dSEric Fiseliernotion that using a ``timespec`` rep in chrono actually makes it act as if it 351d981977dSEric Fiselierwere an actual ``timespec``. 352d981977dSEric Fiselier 353d981977dSEric FiselierInteractions with 32 bit ``time_t`` 354d981977dSEric Fiselier----------------------------------- 355d981977dSEric Fiselier 356d981977dSEric FiselierUp until now we've only be considering cases where ``time_t`` is 64 bits, but what 357d981977dSEric Fiselierabout 32 bit systems/builds where ``time_t`` is 32 bits? (this is the common case 358d981977dSEric Fiselierfor 32 bit builds). 359d981977dSEric Fiselier 360d981977dSEric FiselierWhen ``time_t`` is 32 bits, we can implement ``file_time_type`` simply using 64-bit 361d981977dSEric Fiselier``long long``. There is no need to get either ``__int128_t`` or ``timespec`` emulation 362d981977dSEric Fiselierinvolved. And nor should we, as it would suffer from the numerous complications 363d981977dSEric Fiselierdescribed by this paper. 364d981977dSEric Fiselier 365d981977dSEric FiselierObviously our implementation for 32-bit builds should act as similarly to the 366d981977dSEric Fiselier64-bit build as possible. Code which compiles in one, should compile in the other. 367d981977dSEric FiselierThis consideration is important when choosing between ``__int128_t`` and 368f636411aSLouis Dionneemulating ``timespec``. The solution which provides the most uniformity with 369f636411aSLouis Dionnethe least eccentricity is the preferable one. 370d981977dSEric Fiselier 371d981977dSEric FiselierSummary 372d981977dSEric Fiselier======= 373d981977dSEric Fiselier 374d981977dSEric FiselierThe ``file_time_type`` time point is used to represent the write times for files. 375d981977dSEric FiselierIts job is to act as part of a C++ wrapper for less ideal system interfaces. The 376d981977dSEric Fiselierunderlying filesystem uses the ``timespec`` struct for the same purpose. 377d981977dSEric Fiselier 378d981977dSEric FiselierHowever, the initial implementation of ``file_time_type`` could not represent 379d981977dSEric Fiseliereither the range or resolution of ``timespec``, making it unsuitable. Fixing 380d981977dSEric Fiselierthis requires an implementation which uses more than 64 bits to store the 381d981977dSEric Fiseliertime point. 382d981977dSEric Fiselier 383d981977dSEric FiselierWe primarily considered two solutions: Using ``__int128_t`` and using a 384d981977dSEric Fiselierarithmetic emulation of ``timespec``. Each has its pros and cons, and both 385d981977dSEric Fiseliercome with more than one complication. 386d981977dSEric Fiselier 387d981977dSEric FiselierThe Potential Solutions 388d981977dSEric Fiselier----------------------- 389d981977dSEric Fiselier 390d981977dSEric Fiselier``long long`` - The Status Quo 391d981977dSEric Fiselier~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 392d981977dSEric Fiselier 393d981977dSEric FiselierPros: 394d981977dSEric Fiselier 395f636411aSLouis Dionne* As a type ``long long`` plays the nicest with others: 396d981977dSEric Fiselier 397d981977dSEric Fiselier * It works with streaming operators and other library entities which support 398d981977dSEric Fiselier builtin integer types, but don't support ``__int128_t``. 399d981977dSEric Fiselier * Its the representation used by chrono's ``nanosecond`` and ``second`` typedefs. 400d981977dSEric Fiselier 401d981977dSEric FiselierCons: 402d981977dSEric Fiselier 403d981977dSEric Fiselier* It cannot provide the same resolution as ``timespec`` unless we limit it 404d981977dSEric Fiselier to a range of +/- 300 years from the epoch. 405d981977dSEric Fiselier* It cannot provide the same range as ``timespec`` unless we limit its resolution 406d981977dSEric Fiselier to seconds. 407d981977dSEric Fiselier* ``last_write_time`` has to report an error when the time reported by the filesystem 408d981977dSEric Fiselier is unrepresentable. 409d981977dSEric Fiselier 410d981977dSEric Fiselier__int128_t 411d981977dSEric Fiselier~~~~~~~~~~~ 412d981977dSEric Fiselier 413d981977dSEric FiselierPros: 414d981977dSEric Fiselier 415d981977dSEric Fiselier* It is an integer type. 416f0b379c9SEric Fiselier* It makes the implementation simple and efficient. 417d981977dSEric Fiselier* Acts exactly like other arithmetic types. 418d981977dSEric Fiselier* Can be implicitly converted to a builtin integer type by the user. 419d981977dSEric Fiselier 420d981977dSEric Fiselier * This is important for doing things like: 421d981977dSEric Fiselier 422d981977dSEric Fiselier .. code-block:: cpp 423d981977dSEric Fiselier 424d981977dSEric Fiselier void c_interface_using_time_t(const char* p, time_t); 425d981977dSEric Fiselier 426d981977dSEric Fiselier void foo(path p) { 427d981977dSEric Fiselier file_time_type tp = last_write_time(p); 428d981977dSEric Fiselier time_t secs = duration_cast<seconds>(tp.time_since_epoch()).count(); 429d981977dSEric Fiselier c_interface_using_time_t(p.c_str(), secs); 430d981977dSEric Fiselier } 431d981977dSEric Fiselier 432d981977dSEric FiselierCons: 433d981977dSEric Fiselier 434d981977dSEric Fiselier* It isn't always available (but on 64 bit machines, it normally is). 435d981977dSEric Fiselier* It causes ``file_time_type`` to have a larger range than ``timespec``. 436d981977dSEric Fiselier* It doesn't always act the same as other builtin integer types. For example 437d981977dSEric Fiselier with ``cout`` or ``to_string``. 438d981977dSEric Fiselier* Allows implicit truncation to 64 bit integers. 439d981977dSEric Fiselier* It can be implicitly converted to a builtin integer type by the user, 440d981977dSEric Fiselier truncating its value. 441d981977dSEric Fiselier 442d981977dSEric FiselierArithmetic ``timespec`` Emulation 443d981977dSEric Fiselier~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 444d981977dSEric Fiselier 445d981977dSEric FiselierPros: 446d981977dSEric Fiselier 447d981977dSEric Fiselier* It has the exact same range and resolution of ``timespec`` when representing 448d981977dSEric Fiselier a nanosecond tick period. 449d981977dSEric Fiselier* It's always available, unlike ``__int128_t``. 450d981977dSEric Fiselier 451d981977dSEric FiselierCons: 452d981977dSEric Fiselier 453d981977dSEric Fiselier* It has a larger range when representing any period longer than a nanosecond. 454d981977dSEric Fiselier* Doesn't actually allow users to use it like a ``timespec``. 455d981977dSEric Fiselier* The required representation of using ``tv_sec`` to store the giga tick count 456d981977dSEric Fiselier and ``tv_nsec`` to store the remainder adds nothing over a 128 bit integer, 457d981977dSEric Fiselier but complicates a lot. 458d981977dSEric Fiselier* It isn't a builtin integer type, and can't be used anything like one. 459d981977dSEric Fiselier* Chrono can be made to work with it, but not nicely. 460d981977dSEric Fiselier* Emulating arithmetic classes come with their own host of problems regarding 461d981977dSEric Fiselier overload resolution (Each operator needs three SFINAE constrained versions of 462d981977dSEric Fiselier it in order to act like builtin integer types). 463d981977dSEric Fiselier* It offers little over simply using ``__int128_t``. 464d981977dSEric Fiselier* It acts the most differently than implementations using an actual integer type, 465d981977dSEric Fiselier which has a high chance of breaking source compatibility. 466d981977dSEric Fiselier 467d981977dSEric Fiselier 468d981977dSEric FiselierSelected Solution - Using ``__int128_t`` 469d981977dSEric Fiselier========================================= 470d981977dSEric Fiselier 471d981977dSEric FiselierThe solution I selected for libc++ is using ``__int128_t`` when available, 472d981977dSEric Fiselierand otherwise falling back to using ``long long`` with nanosecond precision. 473d981977dSEric Fiselier 474d981977dSEric FiselierWhen ``__int128_t`` is available, or when ``time_t`` is 32-bits, the implementation 475d981977dSEric Fiselierprovides same resolution and a greater range than ``timespec``. Otherwise 476d981977dSEric Fiselierit still provides the same resolution, but is limited to a range of +/- 300 477d981977dSEric Fiselieryears. This final case should be rather rare, as ``__int128_t`` 478d981977dSEric Fiselieris normally available in 64-bit builds, and ``time_t`` is normally 32-bits 479d981977dSEric Fiselierduring 32-bit builds. 480d981977dSEric Fiselier 481d981977dSEric FiselierAlthough falling back to ``long long`` and nanosecond precision is less than 482d981977dSEric Fiselierideal, it also happens to be the implementation provided by both libstdc++ 483d981977dSEric Fiselierand MSVC. (So that makes it better, right?) 484d981977dSEric Fiselier 485d981977dSEric FiselierAlthough the ``timespec`` emulation solution is feasible and would largely 486d981977dSEric Fiselierdo what we want, it comes with too many complications, potential problems 487d981977dSEric Fiselierand discrepancies when compared to "normal" chrono time points and durations. 488d981977dSEric Fiselier 489d981977dSEric FiselierAn emulation of a builtin arithmetic type using a class is never going to act 490d981977dSEric Fiselierexactly the same, and the difference will be felt by users. It's not reasonable 491d981977dSEric Fiselierto expect them to tolerate and work around these differences. And once 492d981977dSEric Fiselierwe commit to an ABI it will be too late to change. Committing to this seems 493d981977dSEric Fiselierrisky. 494d981977dSEric Fiselier 495d981977dSEric FiselierTherefore, ``__int128_t`` seems like the better solution. 496