2 * Copyright 2017 Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * Conversions between std::chrono types and POSIX time types.
20 * These conversions will fail with a ConversionError if an overflow would
21 * occur performing the conversion. (e.g., if the input value cannot fit in
22 * the destination type). However they allow loss of precision (e.g.,
23 * converting nanoseconds to a struct timeval which only has microsecond
24 * granularity, or a struct timespec to std::chrono::minutes).
30 #include <type_traits>
32 #include <folly/Conv.h>
33 #include <folly/Expected.h>
39 struct is_duration : std::false_type {};
40 template <typename Rep, typename Period>
41 struct is_duration<std::chrono::duration<Rep, Period>> : std::true_type {};
43 struct is_time_point : std::false_type {};
44 template <typename Clock, typename Duration>
45 struct is_time_point<std::chrono::time_point<Clock, Duration>>
48 struct is_std_chrono_type {
49 static constexpr bool value =
50 is_duration<T>::value || is_time_point<T>::value;
53 struct is_posix_time_type {
54 static constexpr bool value = std::is_same<T, struct timespec>::value ||
55 std::is_same<T, struct timeval>::value;
57 template <typename Tgt, typename Src>
58 struct is_chrono_conversion {
59 static constexpr bool value =
60 ((is_std_chrono_type<Tgt>::value && is_posix_time_type<Src>::value) ||
61 (is_posix_time_type<Tgt>::value && is_std_chrono_type<Src>::value));
65 * This converts a number in some input type to time_t while ensuring that it
66 * fits in the range of numbers representable by time_t.
68 * This is similar to the normal folly::tryTo() behavior when converting
69 * arthmetic types to an integer type, except that it does not complain about
70 * floating point conversions losing precision.
72 template <typename Src>
73 Expected<time_t, ConversionCode> chronoRangeCheck(Src value) {
74 if (value > std::numeric_limits<time_t>::max()) {
75 return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW);
77 if (std::is_signed<Src>::value) {
78 if (value < std::numeric_limits<time_t>::lowest()) {
79 return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
83 return static_cast<time_t>(value);
87 * Convert a std::chrono::duration with second granularity to a pair of
88 * (seconds, subseconds)
90 * The SubsecondRatio template parameter specifies what type of subseconds to
91 * return. This must have a numerator of 1.
93 template <typename SubsecondRatio, typename Rep>
94 static Expected<std::pair<time_t, long>, ConversionCode> durationToPosixTime(
95 const std::chrono::duration<Rep, std::ratio<1, 1>>& duration) {
97 SubsecondRatio::num == 1, "subsecond numerator should always be 1");
99 auto sec = chronoRangeCheck(duration.count());
100 if (sec.hasError()) {
101 return makeUnexpected(sec.error());
104 time_t secValue = sec.value();
106 if (std::is_floating_point<Rep>::value) {
107 auto fraction = (duration.count() - secValue);
108 subsec = static_cast<long>(fraction * SubsecondRatio::den);
109 if (duration.count() < 0 && fraction < 0) {
110 if (secValue == std::numeric_limits<time_t>::lowest()) {
111 return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
114 subsec += SubsecondRatio::den;
117 return std::pair<time_t, long>{secValue, subsec};
121 * Convert a std::chrono::duration with subsecond granularity to a pair of
122 * (seconds, subseconds)
124 template <typename SubsecondRatio, typename Rep, std::intmax_t Denominator>
125 static Expected<std::pair<time_t, long>, ConversionCode> durationToPosixTime(
126 const std::chrono::duration<Rep, std::ratio<1, Denominator>>& duration) {
127 static_assert(Denominator != 1, "special case expecting den != 1");
129 SubsecondRatio::num == 1, "subsecond numerator should always be 1");
131 auto sec = chronoRangeCheck(duration.count() / Denominator);
132 if (sec.hasError()) {
133 return makeUnexpected(sec.error());
135 auto secTimeT = sec.value();
137 auto remainder = duration.count() - (secTimeT * Denominator);
138 auto subsec = (remainder * SubsecondRatio::den) / Denominator;
139 if (UNLIKELY(duration.count() < 0) && remainder != 0) {
140 if (secTimeT == std::numeric_limits<time_t>::lowest()) {
141 return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
144 subsec += SubsecondRatio::den;
147 return std::pair<time_t, long>{secTimeT, subsec};
151 * Convert a std::chrono::duration with coarser-than-second granularity to a
152 * pair of (seconds, subseconds)
154 template <typename SubsecondRatio, typename Rep, std::intmax_t Numerator>
155 static Expected<std::pair<time_t, long>, ConversionCode> durationToPosixTime(
156 const std::chrono::duration<Rep, std::ratio<Numerator, 1>>& duration) {
157 static_assert(Numerator != 1, "special case expecting num!=1");
159 SubsecondRatio::num == 1, "subsecond numerator should always be 1");
161 constexpr auto maxValue = std::numeric_limits<time_t>::max() / Numerator;
162 constexpr auto minValue = std::numeric_limits<time_t>::lowest() / Numerator;
163 if (duration.count() > maxValue) {
164 return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW);
166 if (duration.count() < minValue) {
167 return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
170 // Note that we can't use chronoRangeCheck() here since we have to check
171 // if (duration.count() * Numerator) would overflow (which we do above).
172 auto secOriginalRep = (duration.count() * Numerator);
173 auto sec = static_cast<time_t>(secOriginalRep);
176 if (std::is_floating_point<Rep>::value) {
177 auto fraction = secOriginalRep - sec;
178 subsec = static_cast<long>(fraction * SubsecondRatio::den);
179 if (duration.count() < 0 && fraction < 0) {
180 if (sec == std::numeric_limits<time_t>::lowest()) {
181 return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
184 subsec += SubsecondRatio::den;
187 return std::pair<time_t, long>{sec, subsec};
191 * Convert a std::chrono::duration to a pair of (seconds, subseconds)
193 * This overload is only used for unusual durations where neither the numerator
194 * nor denominator are 1.
196 template <typename SubsecondRatio, typename Rep, typename Period>
197 Expected<std::pair<time_t, long>, ConversionCode> durationToPosixTime(
198 const std::chrono::duration<Rep, Period>& duration) {
199 static_assert(Period::num != 1, "should use special-case code when num==1");
200 static_assert(Period::den != 1, "should use special-case code when den==1");
202 SubsecondRatio::num == 1, "subsecond numerator should always be 1");
204 // TODO: We need to implement an overflow-checking tryTo() function for
205 // duration-to-duration casts for the code above to work.
207 // For now this is unimplemented, and we just have a static_assert that
208 // will always fail if someone tries to instantiate this. Unusual duration
209 // types should be extremely rare, and I'm not aware of any code at the
210 // moment that actually needs this.
213 "conversion from unusual duration types is not implemented yet");
215 return makeUnexpected(ConversionCode::SUCCESS);
219 * Check for overflow when converting to a duration type that is second
220 * granularity or finer (e.g., nanoseconds, milliseconds, seconds)
222 * This assumes the input is normalized, with subseconds >= 0 and subseconds
223 * less than 1 second.
225 template <bool IsFloatingPoint>
226 struct CheckOverflowToDuration {
229 typename SubsecondRatio,
232 static ConversionCode check(Seconds seconds, Subseconds subseconds) {
234 Tgt::period::num == 1,
235 "this implementation should only be used for subsecond granularity "
238 !std::is_floating_point<typename Tgt::rep>::value, "incorrect usage");
240 SubsecondRatio::num == 1, "subsecond numerator should always be 1");
242 if (LIKELY(seconds >= 0)) {
243 constexpr auto maxCount = std::numeric_limits<typename Tgt::rep>::max();
244 constexpr auto maxSeconds = maxCount / Tgt::period::den;
246 auto unsignedSeconds =
247 static_cast<typename std::make_unsigned<Seconds>::type>(seconds);
248 if (LIKELY(unsignedSeconds < maxSeconds)) {
249 return ConversionCode::SUCCESS;
252 if (UNLIKELY(unsignedSeconds == maxSeconds)) {
253 constexpr auto maxRemainder =
254 maxCount - (maxSeconds * Tgt::period::den);
255 constexpr auto maxSubseconds =
256 (maxRemainder * SubsecondRatio::den) / Tgt::period::den;
257 if (subseconds <= 0) {
258 return ConversionCode::SUCCESS;
260 if (static_cast<typename std::make_unsigned<Subseconds>::type>(
261 subseconds) <= maxSubseconds) {
262 return ConversionCode::SUCCESS;
265 return ConversionCode::POSITIVE_OVERFLOW;
266 } else if (std::is_unsigned<typename Tgt::rep>::value) {
267 return ConversionCode::NEGATIVE_OVERFLOW;
269 constexpr auto minCount =
270 static_cast<typename std::make_signed<typename Tgt::rep>::type>(
271 std::numeric_limits<typename Tgt::rep>::lowest());
272 constexpr auto minSeconds = (minCount / Tgt::period::den);
273 if (LIKELY(seconds >= minSeconds)) {
274 return ConversionCode::SUCCESS;
277 if (UNLIKELY(seconds == minSeconds - 1)) {
278 constexpr auto maxRemainder =
279 minCount - (minSeconds * Tgt::period::den) + Tgt::period::den;
280 constexpr auto maxSubseconds =
281 (maxRemainder * SubsecondRatio::den) / Tgt::period::den;
282 if (subseconds <= 0) {
283 return ConversionCode::NEGATIVE_OVERFLOW;
285 if (subseconds >= maxSubseconds) {
286 return ConversionCode::SUCCESS;
289 return ConversionCode::NEGATIVE_OVERFLOW;
295 struct CheckOverflowToDuration<true> {
298 typename SubsecondRatio,
301 static ConversionCode check(
302 Seconds /* seconds */,
303 Subseconds /* subseconds */) {
305 std::is_floating_point<typename Tgt::rep>::value, "incorrect usage");
307 SubsecondRatio::num == 1, "subsecond numerator should always be 1");
309 // We expect floating point types to have much a wider representable range
310 // than integer types, so we don't bother actually checking the input
311 // integer value here.
313 std::numeric_limits<typename Tgt::rep>::max() >=
314 std::numeric_limits<Seconds>::max(),
315 "unusually limited floating point type");
317 std::numeric_limits<typename Tgt::rep>::lowest() <=
318 std::numeric_limits<Seconds>::lowest(),
319 "unusually limited floating point type");
321 return ConversionCode::SUCCESS;
326 * Convert a timeval or a timespec to a std::chrono::duration with second
329 * The SubsecondRatio template parameter specifies what type of subseconds to
330 * return. This must have a numerator of 1.
332 * The input must be in normalized form: the subseconds field must be greater
333 * than or equal to 0, and less than SubsecondRatio::den (i.e., less than 1
337 typename SubsecondRatio,
341 auto posixTimeToDuration(
343 Subseconds subseconds,
344 std::chrono::duration<Rep, std::ratio<1, 1>> dummy)
345 -> Expected<decltype(dummy), ConversionCode> {
346 using Tgt = decltype(dummy);
347 static_assert(Tgt::period::num == 1, "special case expecting num==1");
348 static_assert(Tgt::period::den == 1, "special case expecting den==1");
350 SubsecondRatio::num == 1, "subsecond numerator should always be 1");
352 auto outputSeconds = tryTo<typename Tgt::rep>(seconds);
353 if (outputSeconds.hasError()) {
354 return makeUnexpected(outputSeconds.error());
357 if (std::is_floating_point<typename Tgt::rep>::value) {
358 return Tgt{typename Tgt::rep(seconds) +
359 (typename Tgt::rep(subseconds) / SubsecondRatio::den)};
362 // If the value is negative, we have to round up a non-zero subseconds value
363 if (UNLIKELY(outputSeconds.value() < 0) && subseconds > 0) {
365 outputSeconds.value() ==
366 std::numeric_limits<typename Tgt::rep>::lowest())) {
367 return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
369 return Tgt{outputSeconds.value() + 1};
372 return Tgt{outputSeconds.value()};
376 * Convert a timeval or a timespec to a std::chrono::duration with subsecond
380 typename SubsecondRatio,
384 std::intmax_t Denominator>
385 auto posixTimeToDuration(
387 Subseconds subseconds,
388 std::chrono::duration<Rep, std::ratio<1, Denominator>> dummy)
389 -> Expected<decltype(dummy), ConversionCode> {
390 using Tgt = decltype(dummy);
391 static_assert(Tgt::period::num == 1, "special case expecting num==1");
392 static_assert(Tgt::period::den != 1, "special case expecting den!=1");
394 SubsecondRatio::num == 1, "subsecond numerator should always be 1");
396 auto errorCode = detail::CheckOverflowToDuration<
397 std::is_floating_point<typename Tgt::rep>::value>::
398 template check<Tgt, SubsecondRatio>(seconds, subseconds);
399 if (errorCode != ConversionCode::SUCCESS) {
400 return makeUnexpected(errorCode);
403 if (LIKELY(seconds >= 0)) {
404 return std::chrono::duration_cast<Tgt>(
405 std::chrono::duration<typename Tgt::rep>{seconds}) +
406 std::chrono::duration_cast<Tgt>(
407 std::chrono::duration<typename Tgt::rep, SubsecondRatio>{
410 // For negative numbers we have to round subseconds up towards zero, even
411 // though it is a positive value, since the overall value is negative.
412 return std::chrono::duration_cast<Tgt>(
413 std::chrono::duration<typename Tgt::rep>{seconds + 1}) -
414 std::chrono::duration_cast<Tgt>(
415 std::chrono::duration<typename Tgt::rep, SubsecondRatio>{
416 SubsecondRatio::den - subseconds});
421 * Convert a timeval or a timespec to a std::chrono::duration with
422 * granularity coarser than 1 second.
425 typename SubsecondRatio,
429 std::intmax_t Numerator>
430 auto posixTimeToDuration(
432 Subseconds subseconds,
433 std::chrono::duration<Rep, std::ratio<Numerator, 1>> dummy)
434 -> Expected<decltype(dummy), ConversionCode> {
435 using Tgt = decltype(dummy);
436 static_assert(Tgt::period::num != 1, "special case expecting num!=1");
437 static_assert(Tgt::period::den == 1, "special case expecting den==1");
439 SubsecondRatio::num == 1, "subsecond numerator should always be 1");
441 if (UNLIKELY(seconds < 0) && subseconds > 0) {
442 // Increment seconds by one to handle truncation of negative numbers
444 if (UNLIKELY(seconds == std::numeric_limits<Seconds>::lowest())) {
445 return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
450 if (std::is_floating_point<typename Tgt::rep>::value) {
451 // Convert to the floating point type before performing the division
452 return Tgt{static_cast<typename Tgt::rep>(seconds) / Tgt::period::num};
454 // Perform the division as an integer, and check that the result fits in
455 // the output integer type
456 auto outputValue = (seconds / Tgt::period::num);
457 auto expectedOuput = tryTo<typename Tgt::rep>(outputValue);
458 if (expectedOuput.hasError()) {
459 return makeUnexpected(expectedOuput.error());
462 return Tgt{expectedOuput.value()};
467 * Convert a timeval or timespec to a std::chrono::duration
469 * This overload is only used for unusual durations where neither the numerator
470 * nor denominator are 1.
473 typename SubsecondRatio,
477 std::intmax_t Denominator,
478 std::intmax_t Numerator>
479 auto posixTimeToDuration(
481 Subseconds subseconds,
482 std::chrono::duration<Rep, std::ratio<Numerator, Denominator>> dummy)
483 -> Expected<decltype(dummy), ConversionCode> {
484 using Tgt = decltype(dummy);
486 Tgt::period::num != 1, "should use special-case code when num==1");
488 Tgt::period::den != 1, "should use special-case code when den==1");
490 SubsecondRatio::num == 1, "subsecond numerator should always be 1");
492 // TODO: We need to implement an overflow-checking tryTo() function for
493 // duration-to-duration casts for the code above to work.
495 // For now this is unimplemented, and we just have a static_assert that
496 // will always fail if someone tries to instantiate this. Unusual duration
497 // types should be extremely rare, and I'm not aware of any code at the
498 // moment that actually needs this.
500 Tgt::period::num == 1,
501 "conversion to unusual duration types is not implemented yet");
504 return makeUnexpected(ConversionCode::SUCCESS);
509 typename SubsecondRatio,
512 Expected<Tgt, ConversionCode> tryPosixTimeToDuration(
514 Subseconds subseconds) {
516 SubsecondRatio::num == 1, "subsecond numerator should always be 1");
518 // Normalize the input if required
519 if (UNLIKELY(subseconds < 0)) {
520 const auto overflowSeconds = (subseconds / SubsecondRatio::den);
521 const auto remainder = (subseconds % SubsecondRatio::den);
522 if (std::numeric_limits<Seconds>::lowest() + 1 - overflowSeconds >
524 return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
526 seconds = seconds - 1 + overflowSeconds;
527 subseconds = remainder + SubsecondRatio::den;
528 } else if (UNLIKELY(subseconds >= SubsecondRatio::den)) {
529 const auto overflowSeconds = (subseconds / SubsecondRatio::den);
530 const auto remainder = (subseconds % SubsecondRatio::den);
531 if (std::numeric_limits<Seconds>::max() - overflowSeconds < seconds) {
532 return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW);
534 seconds += overflowSeconds;
535 subseconds = remainder;
538 return posixTimeToDuration<SubsecondRatio>(seconds, subseconds, Tgt{});
541 } // namespace detail
544 * struct timespec to std::chrono::duration
546 template <typename Tgt>
547 typename std::enable_if<
548 detail::is_duration<Tgt>::value,
549 Expected<Tgt, ConversionCode>>::type
550 tryTo(const struct timespec& ts) {
551 return detail::tryPosixTimeToDuration<Tgt, std::nano>(ts.tv_sec, ts.tv_nsec);
555 * struct timeval to std::chrono::duration
557 template <typename Tgt>
558 typename std::enable_if<
559 detail::is_duration<Tgt>::value,
560 Expected<Tgt, ConversionCode>>::type
561 tryTo(const struct timeval& tv) {
562 return detail::tryPosixTimeToDuration<Tgt, std::micro>(tv.tv_sec, tv.tv_usec);
566 * timespec or timeval to std::chrono::time_point
568 template <typename Tgt, typename Src>
569 typename std::enable_if<
570 detail::is_time_point<Tgt>::value && detail::is_posix_time_type<Src>::value,
571 Expected<Tgt, ConversionCode>>::type
572 tryTo(const Src& value) {
573 return tryTo<typename Tgt::duration>(value).then(
574 [](typename Tgt::duration result) { return Tgt(result); });
578 * std::chrono::duration to struct timespec
580 template <typename Tgt, typename Rep, typename Period>
581 typename std::enable_if<
582 std::is_same<Tgt, struct timespec>::value,
583 Expected<Tgt, ConversionCode>>::type
584 tryTo(const std::chrono::duration<Rep, Period>& duration) {
585 auto result = detail::durationToPosixTime<std::nano>(duration);
586 if (result.hasError()) {
587 return makeUnexpected(result.error());
591 ts.tv_sec = result.value().first;
592 ts.tv_nsec = result.value().second;
597 * std::chrono::duration to struct timeval
599 template <typename Tgt, typename Rep, typename Period>
600 typename std::enable_if<
601 std::is_same<Tgt, struct timeval>::value,
602 Expected<Tgt, ConversionCode>>::type
603 tryTo(const std::chrono::duration<Rep, Period>& duration) {
604 auto result = detail::durationToPosixTime<std::micro>(duration);
605 if (result.hasError()) {
606 return makeUnexpected(result.error());
610 tv.tv_sec = result.value().first;
611 tv.tv_usec = result.value().second;
616 * std::chrono::time_point to timespec or timeval
618 template <typename Tgt, typename Clock, typename Duration>
619 typename std::enable_if<
620 detail::is_posix_time_type<Tgt>::value,
621 Expected<Tgt, ConversionCode>>::type
622 tryTo(const std::chrono::time_point<Clock, Duration>& timePoint) {
623 return tryTo<Tgt>(timePoint.time_since_epoch());
627 * For all chrono conversions, to() wraps tryTo()
629 template <typename Tgt, typename Src>
630 typename std::enable_if<detail::is_chrono_conversion<Tgt, Src>::value, Tgt>::
632 to(const Src& value) {
633 return tryTo<Tgt>(value).thenOrThrow(
634 [](Tgt res) { return res; },
635 [&](ConversionCode e) { return makeConversionError(e, StringPiece{}); });