Commit dc91b004 authored by Adam Simpkins's avatar Adam Simpkins Committed by Facebook Github Bot

implement to() conversions for std::chrono to timespec/timeval

Summary:
Add folly::to() conversions to convert between std::chrono::duration or
std::chrono::time_point types and struct timespec or struct timeval types.

To conform to the behavior of the existing arithmetic-to-arithmetic
conversions, this code performs proper overflow checking and throws a
`ConversionError` on overflow.  This unfortunately does make the code rather
complicated compared to a non-checking implementation.

Conversions between some unusual duration types is not implemented yet, and
will fail at compile time if someone tries to use it.  This happens for
durations where neither the numerator nor the denominator of the ratio is 1.
For instance, 7/13ths of a second.

Reviewed By: yfeldblum

Differential Revision: D6356700

fbshipit-source-id: 9dce8ab8f32d8c18089f32c7176a8abf3c3f11f7
parent aa7f8dcd
......@@ -327,6 +327,9 @@ if (BUILD_TESTS)
apply_folly_compile_options_to_target(folly_test_support)
folly_define_tests(
DIRECTORY chrono/test/
TEST chrono_conv_test SOURCES ConvTest.cpp
DIRECTORY compression/test/
TEST compression_test SOURCES CompressionTest.cpp
......
......@@ -5,7 +5,7 @@ endif
# Note that the order of SUBDIRS matters.
# Many subdirectories depend on libfollytest from the test directory,
# so it must appear before other directories
SUBDIRS = . test experimental $(MAYBE_INIT) io/test stats/test
SUBDIRS = . test experimental $(MAYBE_INIT) chrono/test io/test stats/test
ACLOCAL_AMFLAGS = -I m4
......@@ -42,6 +42,7 @@ nobase_follyinclude_HEADERS = \
Bits.h \
CachelinePadded.h \
Chrono.h \
chrono/Conv.h \
ClockGettimeWrappers.h \
ConcurrentSkipList.h \
ConcurrentSkipList-inl.h \
......
/*
* Copyright 2017 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Conversions between std::chrono types and POSIX time types.
*
* These conversions will fail with a ConversionError if an overflow would
* occur performing the conversion. (e.g., if the input value cannot fit in
* the destination type). However they allow loss of precision (e.g.,
* converting nanoseconds to a struct timeval which only has microsecond
* granularity, or a struct timespec to std::chrono::minutes).
*/
#pragma once
#include <chrono>
#include <type_traits>
#include <folly/Conv.h>
#include <folly/Expected.h>
namespace folly {
namespace detail {
template <typename T>
struct is_duration : std::false_type {};
template <typename Rep, typename Period>
struct is_duration<std::chrono::duration<Rep, Period>> : std::true_type {};
template <typename T>
struct is_time_point : std::false_type {};
template <typename Clock, typename Duration>
struct is_time_point<std::chrono::time_point<Clock, Duration>>
: std::true_type {};
template <typename T>
struct is_std_chrono_type {
static constexpr bool value =
is_duration<T>::value || is_time_point<T>::value;
};
template <typename T>
struct is_posix_time_type {
static constexpr bool value = std::is_same<T, struct timespec>::value ||
std::is_same<T, struct timeval>::value;
};
template <typename Tgt, typename Src>
struct is_chrono_conversion {
static constexpr bool value =
((is_std_chrono_type<Tgt>::value && is_posix_time_type<Src>::value) ||
(is_posix_time_type<Tgt>::value && is_std_chrono_type<Src>::value));
};
/**
* This converts a number in some input type to time_t while ensuring that it
* fits in the range of numbers representable by time_t.
*
* This is similar to the normal folly::tryTo() behavior when converting
* arthmetic types to an integer type, except that it does not complain about
* floating point conversions losing precision.
*/
template <typename Src>
Expected<time_t, ConversionCode> chronoRangeCheck(Src value) {
if (value > std::numeric_limits<time_t>::max()) {
return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW);
}
if (std::is_signed<Src>::value) {
if (value < std::numeric_limits<time_t>::lowest()) {
return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
}
}
return static_cast<time_t>(value);
}
/**
* Convert a std::chrono::duration with second granularity to a pair of
* (seconds, subseconds)
*
* The SubsecondRatio template parameter specifies what type of subseconds to
* return. This must have a numerator of 1.
*/
template <typename SubsecondRatio, typename Rep>
static Expected<std::pair<time_t, long>, ConversionCode> durationToPosixTime(
const std::chrono::duration<Rep, std::ratio<1, 1>>& duration) {
static_assert(
SubsecondRatio::num == 1, "subsecond numerator should always be 1");
auto sec = chronoRangeCheck(duration.count());
if (sec.hasError()) {
return makeUnexpected(sec.error());
}
time_t secValue = sec.value();
long subsec = 0L;
if (std::is_floating_point<Rep>::value) {
auto fraction = (duration.count() - secValue);
subsec = static_cast<long>(fraction * SubsecondRatio::den);
if (duration.count() < 0 && fraction < 0) {
if (secValue == std::numeric_limits<time_t>::lowest()) {
return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
}
secValue -= 1;
subsec += SubsecondRatio::den;
}
}
return std::pair<time_t, long>{secValue, subsec};
}
/**
* Convert a std::chrono::duration with subsecond granularity to a pair of
* (seconds, subseconds)
*/
template <typename SubsecondRatio, typename Rep, std::intmax_t Denominator>
static Expected<std::pair<time_t, long>, ConversionCode> durationToPosixTime(
const std::chrono::duration<Rep, std::ratio<1, Denominator>>& duration) {
static_assert(Denominator != 1, "special case expecting den != 1");
static_assert(
SubsecondRatio::num == 1, "subsecond numerator should always be 1");
auto sec = chronoRangeCheck(duration.count() / Denominator);
if (sec.hasError()) {
return makeUnexpected(sec.error());
}
auto secTimeT = sec.value();
auto remainder = duration.count() - (secTimeT * Denominator);
auto subsec = (remainder * SubsecondRatio::den) / Denominator;
if (UNLIKELY(duration.count() < 0) && remainder != 0) {
if (secTimeT == std::numeric_limits<time_t>::lowest()) {
return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
}
secTimeT -= 1;
subsec += SubsecondRatio::den;
}
return std::pair<time_t, long>{secTimeT, subsec};
}
/**
* Convert a std::chrono::duration with coarser-than-second granularity to a
* pair of (seconds, subseconds)
*/
template <typename SubsecondRatio, typename Rep, std::intmax_t Numerator>
static Expected<std::pair<time_t, long>, ConversionCode> durationToPosixTime(
const std::chrono::duration<Rep, std::ratio<Numerator, 1>>& duration) {
static_assert(Numerator != 1, "special case expecting num!=1");
static_assert(
SubsecondRatio::num == 1, "subsecond numerator should always be 1");
constexpr auto maxValue = std::numeric_limits<time_t>::max() / Numerator;
constexpr auto minValue = std::numeric_limits<time_t>::lowest() / Numerator;
if (duration.count() > maxValue) {
return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW);
}
if (duration.count() < minValue) {
return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
}
// Note that we can't use chronoRangeCheck() here since we have to check
// if (duration.count() * Numerator) would overflow (which we do above).
auto secOriginalRep = (duration.count() * Numerator);
auto sec = static_cast<time_t>(secOriginalRep);
long subsec = 0L;
if (std::is_floating_point<Rep>::value) {
auto fraction = secOriginalRep - sec;
subsec = static_cast<long>(fraction * SubsecondRatio::den);
if (duration.count() < 0 && fraction < 0) {
if (sec == std::numeric_limits<time_t>::lowest()) {
return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
}
sec -= 1;
subsec += SubsecondRatio::den;
}
}
return std::pair<time_t, long>{sec, subsec};
}
/**
* Convert a std::chrono::duration to a pair of (seconds, subseconds)
*
* This overload is only used for unusual durations where neither the numerator
* nor denominator are 1.
*/
template <typename SubsecondRatio, typename Rep, typename Period>
Expected<std::pair<time_t, long>, ConversionCode> durationToPosixTime(
const std::chrono::duration<Rep, Period>& duration) {
static_assert(Period::num != 1, "should use special-case code when num==1");
static_assert(Period::den != 1, "should use special-case code when den==1");
static_assert(
SubsecondRatio::num == 1, "subsecond numerator should always be 1");
// TODO: We need to implement an overflow-checking tryTo() function for
// duration-to-duration casts for the code above to work.
//
// For now this is unimplemented, and we just have a static_assert that
// will always fail if someone tries to instantiate this. Unusual duration
// types should be extremely rare, and I'm not aware of any code at the
// moment that actually needs this.
static_assert(
Period::num == 1,
"conversion from unusual duration types is not implemented yet");
(void)duration;
return makeUnexpected(ConversionCode::SUCCESS);
}
/**
* Check for overflow when converting to a duration type that is second
* granularity or finer (e.g., nanoseconds, milliseconds, seconds)
*
* This assumes the input is normalized, with subseconds >= 0 and subseconds
* less than 1 second.
*/
template <bool IsFloatingPoint>
struct CheckOverflowToDuration {
template <
typename Tgt,
typename SubsecondRatio,
typename Seconds,
typename Subseconds>
static ConversionCode check(Seconds seconds, Subseconds subseconds) {
static_assert(
Tgt::period::num == 1,
"this implementation should only be used for subsecond granularity "
"duration types");
static_assert(
!std::is_floating_point<typename Tgt::rep>::value, "incorrect usage");
static_assert(
SubsecondRatio::num == 1, "subsecond numerator should always be 1");
if (LIKELY(seconds >= 0)) {
constexpr auto maxCount = std::numeric_limits<typename Tgt::rep>::max();
constexpr auto maxSeconds = maxCount / Tgt::period::den;
auto unsignedSeconds =
static_cast<typename std::make_unsigned<Seconds>::type>(seconds);
if (LIKELY(unsignedSeconds < maxSeconds)) {
return ConversionCode::SUCCESS;
}
if (UNLIKELY(unsignedSeconds == maxSeconds)) {
constexpr auto maxRemainder =
maxCount - (maxSeconds * Tgt::period::den);
constexpr auto maxSubseconds =
(maxRemainder * SubsecondRatio::den) / Tgt::period::den;
if (subseconds <= 0) {
return ConversionCode::SUCCESS;
}
if (static_cast<typename std::make_unsigned<Subseconds>::type>(
subseconds) <= maxSubseconds) {
return ConversionCode::SUCCESS;
}
}
return ConversionCode::POSITIVE_OVERFLOW;
} else if (std::is_unsigned<typename Tgt::rep>::value) {
return ConversionCode::NEGATIVE_OVERFLOW;
} else {
constexpr auto minCount =
static_cast<typename std::make_signed<typename Tgt::rep>::type>(
std::numeric_limits<typename Tgt::rep>::lowest());
constexpr auto minSeconds = (minCount / Tgt::period::den);
if (LIKELY(seconds >= minSeconds)) {
return ConversionCode::SUCCESS;
}
if (UNLIKELY(seconds == minSeconds - 1)) {
constexpr auto maxRemainder =
minCount - (minSeconds * Tgt::period::den) + Tgt::period::den;
constexpr auto maxSubseconds =
(maxRemainder * SubsecondRatio::den) / Tgt::period::den;
if (subseconds <= 0) {
return ConversionCode::NEGATIVE_OVERFLOW;
}
if (subseconds >= maxSubseconds) {
return ConversionCode::SUCCESS;
}
}
return ConversionCode::NEGATIVE_OVERFLOW;
}
}
};
template <>
struct CheckOverflowToDuration<true> {
template <
typename Tgt,
typename SubsecondRatio,
typename Seconds,
typename Subseconds>
static ConversionCode check(
Seconds /* seconds */,
Subseconds /* subseconds */) {
static_assert(
std::is_floating_point<typename Tgt::rep>::value, "incorrect usage");
static_assert(
SubsecondRatio::num == 1, "subsecond numerator should always be 1");
// We expect floating point types to have much a wider representable range
// than integer types, so we don't bother actually checking the input
// integer value here.
static_assert(
std::numeric_limits<typename Tgt::rep>::max() >=
std::numeric_limits<Seconds>::max(),
"unusually limited floating point type");
static_assert(
std::numeric_limits<typename Tgt::rep>::lowest() <=
std::numeric_limits<Seconds>::lowest(),
"unusually limited floating point type");
return ConversionCode::SUCCESS;
}
};
/**
* Helper class to convert a POSIX-style pair of (seconds, subseconds)
* to a std::chrono::duration type.
*
* The SubsecondRatio template parameter specifies what type of subseconds to
* return. This must have a numerator of 1.
*
* The input must be in normalized form: the subseconds field must be greater
* than or equal to 0, and less than SubsecondRatio::den (i.e., less than 1
* second).
*
* This default implementation is only used for unusual std::chrono::duration
* types where neither the numerator nor denominator are 1.
*/
template <typename Tgt>
struct PosixTimeToDuration {
template <typename SubsecondRatio, typename Seconds, typename Subseconds>
static Expected<Tgt, ConversionCode> cast(
Seconds seconds,
Subseconds subseconds);
};
/**
* Convert a timeval or a timespec to a std::chrono::duration with second
* granularity.
*/
template <typename Rep>
struct PosixTimeToDuration<std::chrono::duration<Rep, std::ratio<1, 1>>> {
using Tgt = std::chrono::duration<Rep, std::ratio<1, 1>>;
template <typename SubsecondRatio, typename Seconds, typename Subseconds>
static Expected<Tgt, ConversionCode> cast(
Seconds seconds,
Subseconds subseconds) {
static_assert(Tgt::period::num == 1, "special case expecting num==1");
static_assert(Tgt::period::den == 1, "special case expecting den==1");
static_assert(
SubsecondRatio::num == 1, "subsecond numerator should always be 1");
auto outputSeconds = tryTo<typename Tgt::rep>(seconds);
if (outputSeconds.hasError()) {
return makeUnexpected(outputSeconds.error());
}
if (std::is_floating_point<typename Tgt::rep>::value) {
return Tgt{typename Tgt::rep(seconds) +
(typename Tgt::rep(subseconds) / SubsecondRatio::den)};
}
// If the value is negative, we have to round up a non-zero subseconds value
if (UNLIKELY(outputSeconds.value() < 0) && subseconds > 0) {
if (UNLIKELY(
outputSeconds.value() ==
std::numeric_limits<typename Tgt::rep>::lowest())) {
return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
}
return Tgt{outputSeconds.value() + 1};
}
return Tgt{outputSeconds.value()};
}
};
/**
* Convert a timeval or a timespec to a std::chrono::duration with subsecond
* granularity
*/
template <typename Rep, std::intmax_t Denominator>
struct PosixTimeToDuration<
std::chrono::duration<Rep, std::ratio<1, Denominator>>> {
using Tgt = std::chrono::duration<Rep, std::ratio<1, Denominator>>;
template <typename SubsecondRatio, typename Seconds, typename Subseconds>
static Expected<Tgt, ConversionCode> cast(
Seconds seconds,
Subseconds subseconds) {
static_assert(Tgt::period::num == 1, "special case expecting num==1");
static_assert(Tgt::period::den != 1, "special case expecting den!=1");
static_assert(
SubsecondRatio::num == 1, "subsecond numerator should always be 1");
auto errorCode = detail::CheckOverflowToDuration<
std::is_floating_point<typename Tgt::rep>::value>::
template check<Tgt, SubsecondRatio>(seconds, subseconds);
if (errorCode != ConversionCode::SUCCESS) {
return makeUnexpected(errorCode);
}
if (LIKELY(seconds >= 0)) {
return std::chrono::duration_cast<Tgt>(
std::chrono::duration<typename Tgt::rep>{seconds}) +
std::chrono::duration_cast<Tgt>(
std::chrono::duration<typename Tgt::rep, SubsecondRatio>{
subseconds});
} else {
// For negative numbers we have to round subseconds up towards zero, even
// though it is a positive value, since the overall value is negative.
return std::chrono::duration_cast<Tgt>(
std::chrono::duration<typename Tgt::rep>{seconds + 1}) -
std::chrono::duration_cast<Tgt>(
std::chrono::duration<typename Tgt::rep, SubsecondRatio>{
SubsecondRatio::den - subseconds});
}
}
};
/**
* Convert a timeval or a timespec to a std::chrono::duration with
* granularity coarser than 1 second.
*/
template <typename Rep, std::intmax_t Numerator>
struct PosixTimeToDuration<
std::chrono::duration<Rep, std::ratio<Numerator, 1>>> {
using Tgt = std::chrono::duration<Rep, std::ratio<Numerator, 1>>;
template <typename SubsecondRatio, typename Seconds, typename Subseconds>
static Expected<Tgt, ConversionCode> cast(
Seconds seconds,
Subseconds subseconds) {
static_assert(Tgt::period::num != 1, "special case expecting num!=1");
static_assert(Tgt::period::den == 1, "special case expecting den==1");
static_assert(
SubsecondRatio::num == 1, "subsecond numerator should always be 1");
if (UNLIKELY(seconds < 0) && subseconds > 0) {
// Increment seconds by one to handle truncation of negative numbers
// properly.
if (UNLIKELY(seconds == std::numeric_limits<Seconds>::lowest())) {
return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
}
seconds += 1;
}
if (std::is_floating_point<typename Tgt::rep>::value) {
// Convert to the floating point type before performing the division
return Tgt{static_cast<typename Tgt::rep>(seconds) / Tgt::period::num};
} else {
// Perform the division as an integer, and check that the result fits in
// the output integer type
auto outputValue = (seconds / Tgt::period::num);
auto expectedOuput = tryTo<typename Tgt::rep>(outputValue);
if (expectedOuput.hasError()) {
return makeUnexpected(expectedOuput.error());
}
return Tgt{expectedOuput.value()};
}
}
};
/**
* PosixTimeToDuration::cast() implementation for the default case
* with unusual durations where neither the numerator nor denominator are 1.
*/
template <typename Tgt>
template <typename SubsecondRatio, typename Seconds, typename Subseconds>
Expected<Tgt, ConversionCode> PosixTimeToDuration<Tgt>::cast(
Seconds seconds,
Subseconds subseconds) {
static_assert(
Tgt::period::num != 1, "should use special-case code when num==1");
static_assert(
Tgt::period::den != 1, "should use special-case code when den==1");
static_assert(
SubsecondRatio::num == 1, "subsecond numerator should always be 1");
// TODO: We need to implement an overflow-checking tryTo() function for
// duration-to-duration casts for the code above to work.
//
// For now this is unimplemented, and we just have a static_assert that
// will always fail if someone tries to instantiate this. Unusual duration
// types should be extremely rare, and I'm not aware of any code at the
// moment that actually needs this.
static_assert(
Tgt::period::num == 1,
"conversion to unusual duration types is not implemented yet");
(void)seconds;
(void)subseconds;
return makeUnexpected(ConversionCode::SUCCESS);
}
template <
typename Tgt,
typename SubsecondRatio,
typename Seconds,
typename Subseconds>
Expected<Tgt, ConversionCode> tryPosixTimeToDuration(
Seconds seconds,
Subseconds subseconds) {
static_assert(
SubsecondRatio::num == 1, "subsecond numerator should always be 1");
// Normalize the input if required
if (UNLIKELY(subseconds < 0)) {
const auto overflowSeconds = (subseconds / SubsecondRatio::den);
const auto remainder = (subseconds % SubsecondRatio::den);
if (std::numeric_limits<Seconds>::lowest() + 1 - overflowSeconds >
seconds) {
return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
}
seconds = seconds - 1 + overflowSeconds;
subseconds = remainder + SubsecondRatio::den;
} else if (UNLIKELY(subseconds >= SubsecondRatio::den)) {
const auto overflowSeconds = (subseconds / SubsecondRatio::den);
const auto remainder = (subseconds % SubsecondRatio::den);
if (std::numeric_limits<Seconds>::max() - overflowSeconds < seconds) {
return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW);
}
seconds += overflowSeconds;
subseconds = remainder;
}
using Converter = PosixTimeToDuration<Tgt>;
return Converter::template cast<SubsecondRatio>(seconds, subseconds);
}
} // namespace detail
/**
* struct timespec to std::chrono::duration
*/
template <typename Tgt>
typename std::enable_if<
detail::is_duration<Tgt>::value,
Expected<Tgt, ConversionCode>>::type
tryTo(const struct timespec& ts) {
return detail::tryPosixTimeToDuration<Tgt, std::nano>(ts.tv_sec, ts.tv_nsec);
}
/**
* struct timeval to std::chrono::duration
*/
template <typename Tgt>
typename std::enable_if<
detail::is_duration<Tgt>::value,
Expected<Tgt, ConversionCode>>::type
tryTo(const struct timeval& tv) {
return detail::tryPosixTimeToDuration<Tgt, std::micro>(tv.tv_sec, tv.tv_usec);
}
/**
* timespec or timeval to std::chrono::time_point
*/
template <typename Tgt, typename Src>
typename std::enable_if<
detail::is_time_point<Tgt>::value && detail::is_posix_time_type<Src>::value,
Expected<Tgt, ConversionCode>>::type
tryTo(const Src& value) {
return tryTo<typename Tgt::duration>(value).then(
[](typename Tgt::duration result) { return Tgt(result); });
}
/**
* std::chrono::duration to struct timespec
*/
template <typename Tgt, typename Rep, typename Period>
typename std::enable_if<
std::is_same<Tgt, struct timespec>::value,
Expected<Tgt, ConversionCode>>::type
tryTo(const std::chrono::duration<Rep, Period>& duration) {
auto result = detail::durationToPosixTime<std::nano>(duration);
if (result.hasError()) {
return makeUnexpected(result.error());
}
struct timespec ts;
ts.tv_sec = result.value().first;
ts.tv_nsec = result.value().second;
return ts;
}
/**
* std::chrono::duration to struct timeval
*/
template <typename Tgt, typename Rep, typename Period>
typename std::enable_if<
std::is_same<Tgt, struct timeval>::value,
Expected<Tgt, ConversionCode>>::type
tryTo(const std::chrono::duration<Rep, Period>& duration) {
auto result = detail::durationToPosixTime<std::micro>(duration);
if (result.hasError()) {
return makeUnexpected(result.error());
}
struct timeval tv;
tv.tv_sec = result.value().first;
tv.tv_usec = result.value().second;
return tv;
}
/**
* std::chrono::time_point to timespec or timeval
*/
template <typename Tgt, typename Clock, typename Duration>
typename std::enable_if<
detail::is_posix_time_type<Tgt>::value,
Expected<Tgt, ConversionCode>>::type
tryTo(const std::chrono::time_point<Clock, Duration>& timePoint) {
return tryTo<Tgt>(timePoint.time_since_epoch());
}
/**
* For all chrono conversions, to() wraps tryTo()
*/
template <typename Tgt, typename Src>
typename std::enable_if<detail::is_chrono_conversion<Tgt, Src>::value, Tgt>::
type
to(const Src& value) {
return tryTo<Tgt>(value).thenOrThrow(
[](Tgt res) { return res; },
[&](ConversionCode e) { return makeConversionError(e, StringPiece{}); });
}
} // namespace folly
/*
* Copyright 2004-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/chrono/Conv.h>
#include <folly/portability/GTest.h>
using namespace folly;
using namespace std::chrono;
using namespace std::chrono_literals;
namespace {
/**
* A helper function to create a time_point even if the input duration type has
* finer resolution than the clock duration type.
*/
template <typename Clock, typename Duration>
typename Clock::time_point createTimePoint(const Duration& d) {
return typename Clock::time_point(
std::chrono::duration_cast<typename Clock::duration>(d));
}
} // namespace
TEST(Conv, timespecToStdChrono) {
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 10;
EXPECT_EQ(10ns, to<nanoseconds>(ts));
EXPECT_EQ(0us, to<microseconds>(ts));
EXPECT_EQ(0ms, to<milliseconds>(ts));
EXPECT_EQ(0s, to<seconds>(ts));
ts.tv_sec = 1;
ts.tv_nsec = 10;
EXPECT_EQ(1000000010ns, to<nanoseconds>(ts));
EXPECT_EQ(1000000us, to<microseconds>(ts));
EXPECT_EQ(1000ms, to<milliseconds>(ts));
EXPECT_EQ(1s, to<seconds>(ts));
EXPECT_EQ(
createTimePoint<system_clock>(1000000010ns),
to<system_clock::time_point>(ts));
EXPECT_EQ(
createTimePoint<steady_clock>(1000000010ns),
to<steady_clock::time_point>(ts));
// Test a non-canonical value with tv_nsec larger than 1 second
ts.tv_sec = 5;
ts.tv_nsec = 3219876543;
// Beware about using std::chrono_literals suffixes with very literals:
// older versions of GCC are buggy and would truncate these to 32-bits.
EXPECT_EQ(8219876543LL, to<nanoseconds>(ts).count());
EXPECT_EQ(8219876us, to<microseconds>(ts));
EXPECT_EQ(8219ms, to<milliseconds>(ts));
EXPECT_EQ(8s, to<seconds>(ts));
EXPECT_EQ(
createTimePoint<system_clock>(nanoseconds(8219876543LL)),
to<system_clock::time_point>(ts));
EXPECT_EQ(
createTimePoint<steady_clock>(nanoseconds(8219876543LL)),
to<steady_clock::time_point>(ts));
// Test negative values
// When going to coarser grained types these should be rounded up towards 0.
ts.tv_sec = -5;
ts.tv_nsec = 123456;
EXPECT_EQ(-4999876544, to<nanoseconds>(ts).count());
EXPECT_EQ(-4999876544, duration_cast<nanoseconds>(-5s + 123456ns).count());
EXPECT_EQ(-4999876, to<microseconds>(ts).count());
EXPECT_EQ(-4999876, duration_cast<microseconds>(-5s + 123456ns).count());
EXPECT_EQ(-4999, to<milliseconds>(ts).count());
EXPECT_EQ(-4999, duration_cast<milliseconds>(-5s + 123456ns).count());
EXPECT_EQ(-4s, to<seconds>(ts));
EXPECT_EQ(-4, duration_cast<seconds>(-5s + 123456ns).count());
ts.tv_sec = -7200;
ts.tv_nsec = 123456;
EXPECT_EQ(-1h, to<hours>(ts));
EXPECT_EQ(
-1,
duration_cast<hours>(seconds{ts.tv_sec} + nanoseconds{ts.tv_nsec})
.count());
ts.tv_sec = -7000;
ts.tv_nsec = 123456;
EXPECT_EQ(-1h, to<hours>(ts));
EXPECT_EQ(
-1,
duration_cast<hours>(seconds{ts.tv_sec} + nanoseconds{ts.tv_nsec})
.count());
ts.tv_sec = -7201;
ts.tv_nsec = 123456;
EXPECT_EQ(-2h, to<hours>(ts));
EXPECT_EQ(
-2,
duration_cast<hours>(seconds{ts.tv_sec} + nanoseconds{ts.tv_nsec})
.count());
// Test converions to floating point durations
ts.tv_sec = 1;
ts.tv_nsec = 500000000;
EXPECT_EQ(1.5, to<duration<double>>(ts).count());
ts.tv_sec = -1;
ts.tv_nsec = 500000000;
EXPECT_EQ(-0.5, to<duration<double>>(ts).count());
ts.tv_sec = -1;
ts.tv_nsec = -500000000;
EXPECT_EQ(-1.5, to<duration<double>>(ts).count());
ts.tv_sec = 1;
ts.tv_nsec = 500000000;
auto doubleNanos = to<duration<double, std::nano>>(ts);
EXPECT_EQ(1500000000, doubleNanos.count());
ts.tv_sec = 90;
ts.tv_nsec = 0;
auto doubleMinutes = to<duration<double, std::ratio<60>>>(ts);
EXPECT_EQ(1.5, doubleMinutes.count());
}
TEST(Conv, timespecToStdChronoOverflow) {
struct timespec ts;
// All of our boundary conditions below assume time_t is int64_t.
// This is true on most modern platforms.
if (!std::is_same<decltype(ts.tv_sec), int64_t>::value) {
LOG(INFO) << "skipping most overflow tests: time_t is not int64_t";
} else {
// Test the upper boundary of conversion to uint64_t nanoseconds
using nsec_u64 = std::chrono::duration<uint64_t, std::nano>;
ts.tv_sec = 18446744073;
ts.tv_nsec = 709551615;
EXPECT_EQ(std::numeric_limits<uint64_t>::max(), to<nsec_u64>(ts).count());
ts.tv_nsec += 1;
EXPECT_THROW(to<nsec_u64>(ts), std::range_error);
// Test the lower boundary of conversion to uint64_t nanoseconds
ts.tv_sec = 0;
ts.tv_nsec = 0;
EXPECT_EQ(0, to<nsec_u64>(ts).count());
ts.tv_sec = -1;
ts.tv_nsec = 0;
EXPECT_THROW(to<nsec_u64>(ts), std::range_error);
// Test the upper boundary of conversion to int64_t microseconds
using usec_i64 = std::chrono::duration<int64_t, std::micro>;
ts.tv_sec = 9223372036854LL;
ts.tv_nsec = 775807000;
EXPECT_EQ(std::numeric_limits<int64_t>::max(), to<usec_i64>(ts).count());
ts.tv_nsec += 1;
EXPECT_THROW(to<usec_i64>(ts), std::range_error);
// Test the lower boundary of conversion to int64_t microseconds
ts.tv_sec = -9223372036855LL;
ts.tv_nsec = 224192000;
EXPECT_EQ(std::numeric_limits<int64_t>::min(), to<usec_i64>(ts).count());
ts.tv_nsec -= 1;
EXPECT_THROW(to<usec_i64>(ts), std::range_error);
// Test the boundaries of conversion to int32_t seconds
using sec_i32 = std::chrono::duration<int32_t>;
ts.tv_sec = 2147483647;
ts.tv_nsec = 0;
EXPECT_EQ(std::numeric_limits<int32_t>::max(), to<sec_i32>(ts).count());
ts.tv_nsec = 1000000000;
EXPECT_THROW(to<sec_i32>(ts), std::range_error);
ts.tv_sec = -2147483648;
ts.tv_nsec = 0;
EXPECT_EQ(std::numeric_limits<int32_t>::min(), to<sec_i32>(ts).count());
ts.tv_sec = -2147483649;
ts.tv_nsec = 999999999;
EXPECT_THROW(to<sec_i32>(ts), std::range_error);
ts.tv_sec = -2147483649;
ts.tv_nsec = 0;
EXPECT_THROW(to<sec_i32>(ts), std::range_error);
ts.tv_sec = -2147483650;
ts.tv_nsec = 0;
EXPECT_THROW(to<sec_i32>(ts), std::range_error);
// Test the upper boundary of conversion to uint32_t hours
using hours_u32 = std::chrono::duration<uint32_t, std::ratio<3600>>;
ts.tv_sec = 15461882262000LL;
ts.tv_nsec = 0;
EXPECT_EQ(std::numeric_limits<uint32_t>::max(), to<hours_u32>(ts).count());
ts.tv_sec = 15461882265599LL;
EXPECT_EQ(std::numeric_limits<uint32_t>::max(), to<hours_u32>(ts).count());
ts.tv_sec = 15461882265600LL;
EXPECT_THROW(to<hours_u32>(ts), std::range_error);
using nsec_i64 = std::chrono::duration<int64_t, std::nano>;
ts.tv_sec = std::numeric_limits<int64_t>::max();
ts.tv_nsec = std::numeric_limits<int64_t>::max();
EXPECT_THROW(to<nsec_i64>(ts), std::range_error);
ts.tv_sec = std::numeric_limits<int64_t>::min();
ts.tv_nsec = std::numeric_limits<int64_t>::min();
EXPECT_THROW(to<nsec_i64>(ts), std::range_error);
// Test some non-normal inputs near the int64_t limit
ts.tv_sec = 0;
ts.tv_nsec = std::numeric_limits<int64_t>::min();
EXPECT_EQ(std::numeric_limits<int64_t>::min(), to<nsec_i64>(ts).count());
ts.tv_sec = -1;
ts.tv_nsec = std::numeric_limits<int64_t>::min() + std::nano::den;
EXPECT_EQ(std::numeric_limits<int64_t>::min(), to<nsec_i64>(ts).count());
ts.tv_sec = -1;
ts.tv_nsec = std::numeric_limits<int64_t>::min() + std::nano::den - 1;
EXPECT_THROW(to<nsec_i64>(ts), std::range_error);
ts.tv_sec = 0;
ts.tv_nsec = std::numeric_limits<int64_t>::max();
EXPECT_EQ(std::numeric_limits<int64_t>::max(), to<nsec_i64>(ts).count());
ts.tv_sec = 1;
ts.tv_nsec = std::numeric_limits<int64_t>::max() - std::nano::den;
EXPECT_EQ(std::numeric_limits<int64_t>::max(), to<nsec_i64>(ts).count());
ts.tv_sec = 1;
ts.tv_nsec = std::numeric_limits<int64_t>::max() - std::nano::den + 1;
EXPECT_THROW(to<nsec_i64>(ts), std::range_error);
}
// Theoretically conversion is representable in the output type,
// but we normalize the input first, and normalization would trigger an
// overflow.
using hours_u64 = std::chrono::duration<uint64_t, std::ratio<3600>>;
ts.tv_sec = std::numeric_limits<decltype(ts.tv_sec)>::max();
ts.tv_nsec = 1000000000;
EXPECT_THROW(to<hours_u64>(ts), std::range_error);
// If we drop it back down to the normal range it should succeed
ts.tv_nsec = 999999999;
EXPECT_EQ(
std::numeric_limits<decltype(ts.tv_sec)>::max() / 3600,
to<hours_u64>(ts).count());
}
TEST(Conv, timevalToStdChrono) {
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 10;
EXPECT_EQ(10000ns, to<nanoseconds>(tv));
EXPECT_EQ(10us, to<microseconds>(tv));
EXPECT_EQ(0ms, to<milliseconds>(tv));
EXPECT_EQ(0s, to<seconds>(tv));
tv.tv_sec = 1;
tv.tv_usec = 10;
EXPECT_EQ(1000010000ns, to<nanoseconds>(tv));
EXPECT_EQ(1000010us, to<microseconds>(tv));
EXPECT_EQ(1000ms, to<milliseconds>(tv));
EXPECT_EQ(1s, to<seconds>(tv));
EXPECT_EQ(
createTimePoint<system_clock>(1000010000ns),
to<system_clock::time_point>(tv));
EXPECT_EQ(
createTimePoint<steady_clock>(1000010000ns),
to<steady_clock::time_point>(tv));
// Test a non-canonical value with tv_usec larger than 1 second
tv.tv_sec = 5;
tv.tv_usec = 3219876;
EXPECT_EQ(8219876000LL, to<nanoseconds>(tv).count());
EXPECT_EQ(8219876us, to<microseconds>(tv));
EXPECT_EQ(8219ms, to<milliseconds>(tv));
EXPECT_EQ(8s, to<seconds>(tv));
EXPECT_EQ(
createTimePoint<system_clock>(nanoseconds(8219876000LL)),
to<system_clock::time_point>(tv));
EXPECT_EQ(
createTimePoint<steady_clock>(nanoseconds(8219876000LL)),
to<steady_clock::time_point>(tv));
// Test for overflow.
if (std::numeric_limits<decltype(tv.tv_sec)>::max() >=
std::numeric_limits<int64_t>::max()) {
// Use our own type alias here rather than std::chrono::nanoseconds
// to ensure we have 64-bit rep type.
using nsec_i64 = std::chrono::duration<int64_t, std::nano>;
tv.tv_sec = std::numeric_limits<decltype(tv.tv_sec)>::max();
tv.tv_usec = std::numeric_limits<decltype(tv.tv_usec)>::max();
EXPECT_THROW(to<nsec_i64>(tv), std::range_error);
tv.tv_sec = std::numeric_limits<decltype(tv.tv_sec)>::min();
tv.tv_usec = std::numeric_limits<decltype(tv.tv_usec)>::max();
EXPECT_THROW(to<nsec_i64>(tv), std::range_error);
}
}
TEST(Conv, stdChronoToTimespec) {
auto ts = to<struct timespec>(10ns);
EXPECT_EQ(0, ts.tv_sec);
EXPECT_EQ(10, ts.tv_nsec);
// We don't use std::chrono_literals suffixes here since older
// gcc versions silently truncate the literals to 32-bits.
ts = to<struct timespec>(nanoseconds(987654321012LL));
EXPECT_EQ(987, ts.tv_sec);
EXPECT_EQ(654321012, ts.tv_nsec);
ts = to<struct timespec>(nanoseconds(-987654321012LL));
EXPECT_EQ(-988, ts.tv_sec);
EXPECT_EQ(345678988, ts.tv_nsec);
ts = to<struct timespec>(microseconds(987654321012LL));
EXPECT_EQ(987654, ts.tv_sec);
EXPECT_EQ(321012000, ts.tv_nsec);
ts = to<struct timespec>(milliseconds(987654321012LL));
EXPECT_EQ(987654321, ts.tv_sec);
EXPECT_EQ(12000000, ts.tv_nsec);
ts = to<struct timespec>(seconds(987654321012LL));
EXPECT_EQ(987654321012, ts.tv_sec);
EXPECT_EQ(0, ts.tv_nsec);
ts = to<struct timespec>(10h);
EXPECT_EQ(36000, ts.tv_sec);
EXPECT_EQ(0, ts.tv_nsec);
ts = to<struct timespec>(createTimePoint<steady_clock>(123ns));
EXPECT_EQ(0, ts.tv_sec);
EXPECT_EQ(123, ts.tv_nsec);
ts = to<struct timespec>(createTimePoint<system_clock>(123ns));
EXPECT_EQ(0, ts.tv_sec);
EXPECT_EQ(123, ts.tv_nsec);
}
TEST(Conv, stdChronoToTimespecOverflow) {
EXPECT_THROW(to<uint8_t>(1234), std::range_error);
struct timespec ts;
if (!std::is_same<decltype(ts.tv_sec), int64_t>::value) {
LOG(INFO) << "skipping most overflow tests: time_t is not int64_t";
} else {
// Check for overflow converting from uint64_t seconds to time_t
using sec_u64 = duration<uint64_t>;
ts = to<struct timespec>(sec_u64(9223372036854775807ULL));
EXPECT_EQ(ts.tv_sec, 9223372036854775807ULL);
EXPECT_EQ(ts.tv_nsec, 0);
EXPECT_THROW(
to<struct timespec>(sec_u64(9223372036854775808ULL)), std::range_error);
// Check for overflow converting from int64_t hours to time_t
using hours_i64 = duration<int64_t, std::ratio<3600>>;
ts = to<struct timespec>(hours_i64(2562047788015215LL));
EXPECT_EQ(ts.tv_sec, 9223372036854774000LL);
EXPECT_EQ(ts.tv_nsec, 0);
EXPECT_THROW(
to<struct timespec>(hours_i64(2562047788015216LL)), std::range_error);
}
// Test for overflow.
// Use a custom hours type using time_t as the underlying storage type to
// guarantee that we can overflow.
using hours_timet = std::chrono::duration<time_t, std::ratio<3600>>;
EXPECT_THROW(
to<struct timespec>(hours_timet(std::numeric_limits<time_t>::max())),
std::range_error);
}
TEST(Conv, stdChronoToTimeval) {
auto tv = to<struct timeval>(10ns);
EXPECT_EQ(0, tv.tv_sec);
EXPECT_EQ(0, tv.tv_usec);
tv = to<struct timeval>(10us);
EXPECT_EQ(0, tv.tv_sec);
EXPECT_EQ(10, tv.tv_usec);
tv = to<struct timeval>(nanoseconds(987654321012LL));
EXPECT_EQ(987, tv.tv_sec);
EXPECT_EQ(654321, tv.tv_usec);
tv = to<struct timeval>(nanoseconds(-987654321012LL));
EXPECT_EQ(-988, tv.tv_sec);
EXPECT_EQ(345679, tv.tv_usec);
tv = to<struct timeval>(microseconds(987654321012LL));
EXPECT_EQ(987654, tv.tv_sec);
EXPECT_EQ(321012, tv.tv_usec);
tv = to<struct timeval>(milliseconds(987654321012LL));
EXPECT_EQ(987654321, tv.tv_sec);
EXPECT_EQ(12000, tv.tv_usec);
tv = to<struct timeval>(seconds(987654321012LL));
EXPECT_EQ(987654321012, tv.tv_sec);
EXPECT_EQ(0, tv.tv_usec);
// Try converting fractional seconds
tv = to<struct timeval>(duration<double>{3.456789});
EXPECT_EQ(3, tv.tv_sec);
EXPECT_EQ(456789, tv.tv_usec);
tv = to<struct timeval>(duration<double>{-3.456789});
EXPECT_EQ(-4, tv.tv_sec);
EXPECT_EQ(543211, tv.tv_usec);
// Try converting fractional hours
tv = to<struct timeval>(duration<double, std::ratio<3600>>{3.456789});
EXPECT_EQ(12444, tv.tv_sec);
// The usec field is generally off-by-one due to
// floating point rounding error
EXPECT_NEAR(440400, tv.tv_usec, 1);
tv = to<struct timeval>(duration<double, std::ratio<3600>>{-3.456789});
EXPECT_EQ(-12445, tv.tv_sec);
EXPECT_NEAR(559600, tv.tv_usec, 1);
// Try converting fractional milliseconds
tv = to<struct timeval>(duration<double, std::milli>{9123.456789});
EXPECT_EQ(9, tv.tv_sec);
EXPECT_EQ(123456, tv.tv_usec);
tv = to<struct timeval>(duration<double, std::milli>{-9123.456789});
EXPECT_EQ(-10, tv.tv_sec);
EXPECT_NEAR(876544, tv.tv_usec, 1);
tv = to<struct timeval>(duration<uint32_t, std::ratio<3600>>{3});
EXPECT_EQ(10800, tv.tv_sec);
EXPECT_EQ(0, tv.tv_usec);
tv = to<struct timeval>(duration<uint32_t, std::nano>{3123});
EXPECT_EQ(0, tv.tv_sec);
EXPECT_EQ(3, tv.tv_usec);
tv = to<struct timeval>(duration<int32_t, std::nano>{-3123});
EXPECT_EQ(-1, tv.tv_sec);
EXPECT_EQ(999997, tv.tv_usec);
tv = to<struct timeval>(createTimePoint<steady_clock>(123us));
EXPECT_EQ(0, tv.tv_sec);
EXPECT_EQ(123, tv.tv_usec);
tv = to<struct timeval>(createTimePoint<system_clock>(123us));
EXPECT_EQ(0, tv.tv_sec);
EXPECT_EQ(123, tv.tv_usec);
}
ACLOCAL_AMFLAGS = -I m4
CPPFLAGS = -I$(top_srcdir)/test/gtest/googletest/include
ldadd = $(top_builddir)/test/libfollytestmain.la
check_PROGRAMS = \
conv_test
TESTS = $(check_PROGRAMS)
conv_test_SOURCES = ConvTest.cpp
conv_test_LDADD = $(ldadd)
......@@ -632,6 +632,7 @@ FB_FILTER_PKG_LIBS([$AM_LDFLAGS $LIBS])
# Output
AC_CONFIG_FILES([Makefile
chrono/test/Makefile
io/test/Makefile
libfolly.pc
test/Makefile
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment