Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion include/time_shield/constants.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,10 @@ namespace time_shield {
constexpr int64_t MAX_YEAR = 292277022000LL; ///< Maximum representable year
constexpr int64_t MIN_YEAR = -2967369602200LL; ///< Minimum representable year
constexpr int64_t ERROR_YEAR = 9223372036854770000LL; ///< Error year value
constexpr int64_t MAX_TIMESTAMP = 9223371891821347199LL; ///< Maximum timestamp value
constexpr int64_t MAX_TIMESTAMP = (((std::numeric_limits<int64_t>::max)() - (MS_PER_SEC - 1)) / MS_PER_SEC) - (SEC_PER_YEAR - 1); ///< Maximum timestamp value
constexpr int64_t MIN_TIMESTAMP = -MAX_TIMESTAMP; ///< Minimum timestamp value
constexpr int64_t MAX_TIMESTAMP_MS = MAX_TIMESTAMP * MS_PER_SEC + (MS_PER_SEC - 1); ///< Maximum timestamp value in milliseconds
constexpr int64_t MIN_TIMESTAMP_MS = MIN_TIMESTAMP * MS_PER_SEC; ///< Minimum timestamp value in milliseconds
constexpr int64_t ERROR_TIMESTAMP = 9223372036854770000LL; ///< Error timestamp value
constexpr double MAX_OADATE = (std::numeric_limits<double>::max)(); ///< Maximum OLE automation date
constexpr double AVG_DAYS_PER_YEAR = 365.25; ///< Average days per year
Expand Down
181 changes: 74 additions & 107 deletions include/time_shield/date_time_conversions.hpp

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions include/time_shield/detail/floor_math.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
#pragma once
#ifndef _TIME_SHIELD_DETAIL_FLOOR_MATH_HPP_INCLUDED
#define _TIME_SHIELD_DETAIL_FLOOR_MATH_HPP_INCLUDED

/// \file floor_math.hpp
/// \brief Floor division and modulus helpers.

namespace time_shield {
namespace detail {

/// \brief Floor division for positive divisor.
template<class T>
TIME_SHIELD_CONSTEXPR inline T floor_div(T a, T b) noexcept {
T q = a / b;
T r = a % b;
if (r != 0 && a < 0) --q;
return q;
}

/// \brief Floor-mod for positive modulus (returns r in [0..b)).
template<class T>
TIME_SHIELD_CONSTEXPR inline T floor_mod(T a, T b) noexcept {
T r = a % b;
if (r < 0) r += b;
return r;
}

} // namespace detail
} // namespace time_shield

#endif // _TIME_SHIELD_DETAIL_FLOOR_MATH_HPP_INCLUDED
97 changes: 87 additions & 10 deletions include/time_shield/time_unit_conversions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "config.hpp"
#include "constants.hpp"
#include "detail/floor_math.hpp"
#include "types.hpp"

#include <cmath>
Expand All @@ -24,8 +25,8 @@ namespace time_shield {
/// \return T Nanosecond part of the second.
template<class T = int>
TIME_SHIELD_CONSTEXPR T ns_of_sec(fts_t ts) noexcept {
fts_t temp = 0;
return static_cast<T>(std::round(std::modf(ts, &temp) * static_cast<fts_t>(NS_PER_SEC)));
const int64_t ns = static_cast<int64_t>(std::floor(ts * static_cast<fts_t>(NS_PER_SEC)));
return static_cast<T>(detail::floor_mod<int64_t>(ns, NS_PER_SEC));
}

/// \brief Get the microsecond part of the second from a floating-point timestamp.
Expand All @@ -34,8 +35,8 @@ namespace time_shield {
/// \return T Microsecond part of the second.
template<class T = int>
TIME_SHIELD_CONSTEXPR T us_of_sec(fts_t ts) noexcept {
fts_t temp = 0;
return static_cast<T>(std::round(std::modf(ts, &temp) * static_cast<fts_t>(US_PER_SEC)));
const int64_t us = static_cast<int64_t>(std::floor(ts * static_cast<fts_t>(US_PER_SEC)));
return static_cast<T>(detail::floor_mod<int64_t>(us, US_PER_SEC));
}

/// \brief Get the millisecond part of the second from a floating-point timestamp.
Expand All @@ -44,6 +45,36 @@ namespace time_shield {
/// \return T Millisecond part of the second.
template<class T = int>
TIME_SHIELD_CONSTEXPR T ms_of_sec(fts_t ts) noexcept {
const int64_t ms = static_cast<int64_t>(std::floor(ts * static_cast<fts_t>(MS_PER_SEC)));
return static_cast<T>(detail::floor_mod<int64_t>(ms, MS_PER_SEC));
}

/// \brief Get the nanosecond part of the second from a floating-point timestamp (truncating).
/// \tparam T Type of the returned value (default is int).
/// \param ts Timestamp in floating-point seconds.
/// \return T Nanosecond part of the second, truncating toward zero.
template<class T = int>
TIME_SHIELD_CONSTEXPR T ns_of_sec_signed(fts_t ts) noexcept {
fts_t temp = 0;
return static_cast<T>(std::round(std::modf(ts, &temp) * static_cast<fts_t>(NS_PER_SEC)));
}

/// \brief Get the microsecond part of the second from a floating-point timestamp (truncating).
/// \tparam T Type of the returned value (default is int).
/// \param ts Timestamp in floating-point seconds.
/// \return T Microsecond part of the second, truncating toward zero.
template<class T = int>
TIME_SHIELD_CONSTEXPR T us_of_sec_signed(fts_t ts) noexcept {
fts_t temp = 0;
return static_cast<T>(std::round(std::modf(ts, &temp) * static_cast<fts_t>(US_PER_SEC)));
}

/// \brief Get the millisecond part of the second from a floating-point timestamp (truncating).
/// \tparam T Type of the returned value (default is int).
/// \param ts Timestamp in floating-point seconds.
/// \return T Millisecond part of the second, truncating toward zero.
template<class T = int>
TIME_SHIELD_CONSTEXPR T ms_of_sec_signed(fts_t ts) noexcept {
fts_t temp = 0;
return static_cast<T>(std::round(std::modf(ts, &temp) * static_cast<fts_t>(MS_PER_SEC)));
}
Expand All @@ -53,8 +84,44 @@ namespace time_shield {
/// \param ts Timestamp in milliseconds.
/// \return T Millisecond part of the timestamp.
template<class T = int>
constexpr T ms_part(ts_ms_t ts) noexcept {
return static_cast<T>(detail::floor_mod<int64_t>(static_cast<int64_t>(ts), MS_PER_SEC));
}

/// \brief Alias for ms_part.
/// \tparam T Type of the returned value (default is int).
/// \param ts Timestamp in milliseconds.
/// \return T Millisecond part of the timestamp.
template<class T = int>
constexpr T ms_of_ts(ts_ms_t ts) noexcept {
return static_cast<T>(ts % MS_PER_SEC);
return ms_part<T>(ts);
}

/// \brief Get the microsecond part of the timestamp.
/// \tparam T Type of the returned value (default is int).
/// \param ts Timestamp in microseconds.
/// \return T Microsecond part of the timestamp.
template<class T = int>
constexpr T us_part(ts_us_t ts) noexcept {
return static_cast<T>(detail::floor_mod<int64_t>(static_cast<int64_t>(ts), US_PER_SEC));
}

/// \brief Alias for us_part.
/// \tparam T Type of the returned value (default is int).
/// \param ts Timestamp in microseconds.
/// \return T Microsecond part of the timestamp.
template<class T = int>
constexpr T us_of_ts(ts_us_t ts) noexcept {
return us_part<T>(ts);
}

/// \brief Get the nanosecond part of the timestamp.
/// \tparam T Type of the returned value (default is int).
/// \param ts Timestamp in nanoseconds.
/// \return T Nanosecond part of the timestamp.
template<class T = int, class T2 = int64_t>
constexpr T ns_part(T2 ts) noexcept {
return static_cast<T>(detail::floor_mod<int64_t>(static_cast<int64_t>(ts), NS_PER_SEC));
}

# ifndef TIME_SHIELD_CPP17
Expand Down Expand Up @@ -115,7 +182,9 @@ namespace time_shield {
/// \return T1 Timestamp in seconds.
template<class T1 = ts_t, class T2 = ts_ms_t>
constexpr T1 ms_to_sec(T2 ts_ms) noexcept {
return static_cast<T1>(ts_ms) / static_cast<T1>(MS_PER_SEC);
return static_cast<T1>(detail::floor_div(
static_cast<int64_t>(ts_ms),
static_cast<int64_t>(MS_PER_SEC)));
}

/// \brief Converts a timestamp from milliseconds to floating-point seconds.
Expand Down Expand Up @@ -181,7 +250,9 @@ namespace time_shield {
/// \return T1 Timestamp in minutes.
template<class T1 = int, class T2 = ts_ms_t>
constexpr T1 ms_to_min(T2 ts) noexcept {
return static_cast<T1>(ts) / static_cast<T1>(MS_PER_MIN);
return static_cast<T1>(detail::floor_div(
static_cast<int64_t>(ts),
static_cast<int64_t>(MS_PER_MIN)));
}

//----------------------------------------------------------------------------//
Expand Down Expand Up @@ -238,7 +309,9 @@ namespace time_shield {
/// \return T1 Timestamp in minutes.
template<class T1 = int, class T2 = ts_t>
constexpr T1 sec_to_min(T2 ts) noexcept {
return static_cast<T1>(ts) / static_cast<T1>(SEC_PER_MIN);
return static_cast<T1>(detail::floor_div(
static_cast<int64_t>(ts),
static_cast<int64_t>(SEC_PER_MIN)));
}

/// \brief Converts a timestamp from minutes to floating-point seconds.
Expand Down Expand Up @@ -314,7 +387,9 @@ namespace time_shield {
/// \return T1 Timestamp in hours.
template<class T1 = int, class T2 = ts_ms_t>
constexpr T1 ms_to_hour(T2 ts) noexcept {
return static_cast<T1>(ts) / static_cast<T1>(MS_PER_HOUR);
return static_cast<T1>(detail::floor_div(
static_cast<int64_t>(ts),
static_cast<int64_t>(MS_PER_HOUR)));
}

//----------------------------------------------------------------------------//
Expand Down Expand Up @@ -372,7 +447,9 @@ namespace time_shield {
/// \return T1 Timestamp in hours.
template<class T1 = int, class T2 = ts_t>
constexpr T1 sec_to_hour(T2 ts) noexcept {
return static_cast<T1>(ts) / static_cast<T1>(SEC_PER_HOUR);
return static_cast<T1>(detail::floor_div(
static_cast<int64_t>(ts),
static_cast<int64_t>(SEC_PER_HOUR)));
}

/// \brief Converts a timestamp from hours to floating-point seconds.
Expand Down
46 changes: 46 additions & 0 deletions tests/negative_time_boundaries_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include <time_shield/time_conversions.hpp>

#include <cassert>

int main() {
using namespace time_shield;

const ts_t pre_epoch = to_timestamp(1969, 12, 31, 23, 59, 59);
const ts_t pre_epoch_start = to_timestamp(1969, 12, 31, 0, 0, 0);
const ts_t pre_epoch_prev_day = to_timestamp(1969, 12, 30, 0, 0, 0);

assert(pre_epoch == -1);
assert(start_of_day(pre_epoch) == pre_epoch_start);
assert(start_of_day(pre_epoch_start) == pre_epoch_start);
assert(start_of_day(-86400) == pre_epoch_start);
assert(start_of_prev_day(pre_epoch_start) == pre_epoch_prev_day);
assert(end_of_day(pre_epoch) == to_timestamp(1969, 12, 31, 23, 59, 59));
assert(end_of_day(pre_epoch_start) == to_timestamp(1969, 12, 31, 23, 59, 59));

assert(start_of_hour(pre_epoch) == to_timestamp(1969, 12, 31, 23, 0, 0));
assert(end_of_hour(pre_epoch) == to_timestamp(1969, 12, 31, 23, 59, 59));
assert(start_of_min(pre_epoch) == to_timestamp(1969, 12, 31, 23, 59, 0));
assert(end_of_min(pre_epoch) == to_timestamp(1969, 12, 31, 23, 59, 59));

assert(min_of_day(pre_epoch) == 1439);
assert(hour_of_day(pre_epoch) == 23);
assert(min_of_hour(pre_epoch) == 59);
assert(weekday_of_ts(pre_epoch) == WED);

assert(start_of_period(300, pre_epoch) == -300);
assert(end_of_period(300, pre_epoch) == -1);

const ts_ms_t pre_epoch_ms = to_timestamp_ms(1969, 12, 31, 23, 59, 59, 999);
const ts_ms_t pre_epoch_start_ms = sec_to_ms(pre_epoch_start);

assert(pre_epoch_ms == -1);
assert(start_of_day_ms(pre_epoch_ms) == pre_epoch_start_ms);
assert(start_of_day_ms(pre_epoch_start_ms - 1) == sec_to_ms(pre_epoch_prev_day));
assert(end_of_day_ms(pre_epoch_ms) == pre_epoch_start_ms + MS_PER_DAY - 1);
assert(start_of_hour_ms(pre_epoch_ms) == sec_to_ms(start_of_hour(pre_epoch)));
assert(start_of_hour_ms(-1000) == sec_to_ms(to_timestamp(1969, 12, 31, 23, 0, 0)));
assert(start_of_hour_ms(-1001) == sec_to_ms(to_timestamp(1969, 12, 31, 23, 0, 0)));
assert(end_of_hour_ms(pre_epoch_ms) == sec_to_ms(end_of_hour(pre_epoch)) + 999);

return 0;
}
57 changes: 57 additions & 0 deletions tests/test_timestamp_ms_pre_epoch.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <time_shield/time_conversions.hpp>
#include <time_shield/detail/floor_math.hpp>

#include <cassert>
#include <cstdint>

namespace {
int64_t expected_ts_ms(int64_t year, int month, int day, int hour, int min, int sec, int ms) {
const int64_t unix_day = static_cast<int64_t>(time_shield::date_to_unix_day(year, month, day));
int64_t sec_value = unix_day * static_cast<int64_t>(time_shield::SEC_PER_DAY) +
static_cast<int64_t>(hour) * static_cast<int64_t>(time_shield::SEC_PER_HOUR) +
static_cast<int64_t>(min) * static_cast<int64_t>(time_shield::SEC_PER_MIN) +
static_cast<int64_t>(sec);
int64_t ms_value = static_cast<int64_t>(ms);
sec_value += time_shield::detail::floor_div(ms_value, static_cast<int64_t>(time_shield::MS_PER_SEC));
ms_value = time_shield::detail::floor_mod(ms_value, static_cast<int64_t>(time_shield::MS_PER_SEC));
return sec_value * static_cast<int64_t>(time_shield::MS_PER_SEC) + ms_value;
}
}

int main() {
using namespace time_shield;

assert(to_timestamp_ms(1969, 12, 31, 23, 59, 59, 0) == -1000);
assert(to_timestamp_ms(1969, 12, 31, 23, 59, 59, 1) == -999);
assert(to_timestamp_ms(1969, 12, 31, 23, 59, 59, 500) == -500);
assert(to_timestamp_ms(1969, 12, 31, 23, 59, 59, 999) == -1);
assert(to_timestamp_ms(1970, 1, 1, 0, 0, 0, 0) == 0);
assert(to_timestamp_ms(1970, 1, 1, 0, 0, 0, 1) == 1);

assert(to_timestamp_ms(1969, 12, 31, 23, 59, 59, 1000) ==
expected_ts_ms(1969, 12, 31, 23, 59, 59, 1000));
assert(to_timestamp_ms(1969, 12, 31, 23, 59, 59, -1) ==
expected_ts_ms(1969, 12, 31, 23, 59, 59, -1));
assert(to_timestamp_ms(1969, 12, 31, 23, 59, 59, -1000) ==
expected_ts_ms(1969, 12, 31, 23, 59, 59, -1000));
assert(to_timestamp_ms(1969, 12, 31, 23, 59, 59, -1001) ==
expected_ts_ms(1969, 12, 31, 23, 59, 59, -1001));
assert(to_timestamp_ms(1969, 12, 31, 23, 59, 59, 1500) ==
expected_ts_ms(1969, 12, 31, 23, 59, 59, 1500));
assert(to_timestamp_ms(1970, 1, 1, 0, 0, 0, -1) ==
expected_ts_ms(1970, 1, 1, 0, 0, 0, -1));
assert(to_timestamp_ms(1970, 1, 1, 0, 0, 0, 1234567) ==
expected_ts_ms(1970, 1, 1, 0, 0, 0, 1234567));

assert(to_timestamp_ms(1900, 3, 1, 0, 0, 0, 123) ==
expected_ts_ms(1900, 3, 1, 0, 0, 0, 123));
assert(to_timestamp_ms(2000, 2, 29, 12, 34, 56, 789) ==
expected_ts_ms(2000, 2, 29, 12, 34, 56, 789));
assert(to_timestamp_ms(2100, 3, 1, 0, 0, 0, 0) ==
expected_ts_ms(2100, 3, 1, 0, 0, 0, 0));

DateTimeStruct dt{1969, 12, 31, 23, 59, 59, 500};
assert(dt_to_timestamp_ms(dt) == to_timestamp_ms(1969, 12, 31, 23, 59, 59, 500));

return 0;
}
77 changes: 77 additions & 0 deletions tests/test_year_boundaries_ms.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#include <time_shield/time_conversions.hpp>
#include <time_shield/detail/floor_math.hpp>

#include <array>
#include <cassert>
#include <cstdint>

int main() {
using namespace time_shield;

const auto check_ms = [](ts_ms_t ts_ms) {
const int64_t sec_floor = detail::floor_div<int64_t>(
static_cast<int64_t>(ts_ms), MS_PER_SEC);
const ts_t start_sec = start_of_year(static_cast<ts_t>(sec_floor));
const ts_t end_sec = end_of_year(static_cast<ts_t>(sec_floor));
const ts_ms_t ref_start_ms = static_cast<ts_ms_t>(start_sec * MS_PER_SEC);
const ts_ms_t ref_end_ms = static_cast<ts_ms_t>(end_sec * MS_PER_SEC + MS_PER_SEC - 1);
assert(start_of_year_ms(ts_ms) == ref_start_ms);
assert(end_of_year_ms(ts_ms) == ref_end_ms);
assert(ref_start_ms <= ts_ms);
assert(ts_ms <= ref_end_ms);
};

const std::array<ts_ms_t, 10> near_epoch_ms = {
-2001, -2000, -1001, -1000, -999, -2, -1, 0, 1, 999
};
for (ts_ms_t ts_ms : near_epoch_ms) {
check_ms(ts_ms);
}
check_ms(1000);
check_ms(1001);

const std::array<int64_t, 10> years = {
1969, 1970, 1971, 1972, 1900, 2000, 2100, 2400, 1600, -2400
};
const std::array<int64_t, 7> start_offsets = {
-1000, -999, -1, 0, 1, 999, 1000
};
const std::array<int64_t, 5> end_offsets = {
-1000, -1, 0, 1, 1000
};

for (int64_t year : years) {
const ts_t year_start_sec = to_timestamp(year, 1, 1, 0, 0, 0);
const ts_ms_t year_start_ms = static_cast<ts_ms_t>(year_start_sec * MS_PER_SEC);
for (int64_t offset : start_offsets) {
check_ms(year_start_ms + offset);
}

const ts_t year_mid_sec = to_timestamp(year, 6, 1, 0, 0, 0);
const ts_t year_end_sec = end_of_year(year_mid_sec);
const ts_ms_t year_end_ms = static_cast<ts_ms_t>(year_end_sec * MS_PER_SEC + MS_PER_SEC - 1);
for (int64_t offset : end_offsets) {
check_ms(year_end_ms + offset);
}
}

const std::array<ts_ms_t, 11> extremes = {
MIN_TIMESTAMP_MS + 1,
MIN_TIMESTAMP_MS + 999,
MIN_TIMESTAMP_MS + 1000,
MIN_TIMESTAMP_MS,
MIN_TIMESTAMP_MS - 1,
MIN_TIMESTAMP_MS - 999,
MIN_TIMESTAMP_MS - 1000,
MAX_TIMESTAMP_MS,
MAX_TIMESTAMP_MS - 1,
MAX_TIMESTAMP_MS - 999,
MAX_TIMESTAMP_MS - 1000
};
for (ts_ms_t ts_ms : extremes) {
check_ms(ts_ms);
}

(void)detail::floor_mod<int64_t>(0, 1);
return 0;
}
Loading