implement to() conversions for std::chrono to timespec/timeval
authorAdam Simpkins <simpkins@fb.com>
Sun, 19 Nov 2017 23:14:20 +0000 (15:14 -0800)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Sun, 19 Nov 2017 23:31:10 +0000 (15:31 -0800)
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

CMakeLists.txt
folly/Makefile.am
folly/chrono/Conv.h [new file with mode: 0644]
folly/chrono/test/ConvTest.cpp [new file with mode: 0644]
folly/chrono/test/Makefile.am [new file with mode: 0644]
folly/configure.ac

index 699489b..3d7c153 100755 (executable)
@@ -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
 
index 0639ff9..2bf6bae 100644 (file)
@@ -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 \
diff --git a/folly/chrono/Conv.h b/folly/chrono/Conv.h
new file mode 100644 (file)
index 0000000..95aff32
--- /dev/null
@@ -0,0 +1,638 @@
+/*
+ * 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
diff --git a/folly/chrono/test/ConvTest.cpp b/folly/chrono/test/ConvTest.cpp
new file mode 100644 (file)
index 0000000..172bdd6
--- /dev/null
@@ -0,0 +1,446 @@
+/*
+ * 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);
+}
diff --git a/folly/chrono/test/Makefile.am b/folly/chrono/test/Makefile.am
new file mode 100644 (file)
index 0000000..5ea95a0
--- /dev/null
@@ -0,0 +1,12 @@
+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)
index 826972e..0e6aa37 100644 (file)
@@ -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