xref: /llvm-project/libcxx/docs/DesignDocs/FileTimeType.rst (revision 11e2975810acd6abde9071818e03634d99492b54)
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