diff --git a/Doxyfile b/Doxyfile
index 419b742a3ebf4acbc6929bfea78c59b610269b4d..713a0c961b63058f89cd68f9fbb437732586a0fa 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -1534,7 +1534,7 @@ FORMULA_TRANSPARENT    = YES
 # The default value is: NO.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
-USE_MATHJAX            = NO
+USE_MATHJAX            = YES
 
 # When MathJax is enabled you can set the default output format to be used for
 # the MathJax output. See the MathJax site (see:
diff --git a/inc/ErrorHandler.hpp b/inc/ErrorHandler.hpp
index 351a75caba2833cef379c3d5af9549de4022572c..6c3d1f74ef02b4cc01e0725a665af4d085207f86 100644
--- a/inc/ErrorHandler.hpp
+++ b/inc/ErrorHandler.hpp
@@ -93,6 +93,10 @@ public:
 		 * Invalid TimeStamp parameters at creation
 		 */
 		InvalidTimeStampInput = 15,
+		/**
+		 * Timestamp out of bounds to be stored or converted
+		 */
+		TimeStampOutOfBounds = 16,
 	};
 
 	/**
diff --git a/inc/Platform/x86/ECSS_Configuration.hpp b/inc/Platform/x86/ECSS_Configuration.hpp
index 1871be15d8593871f00866fb9b167262d55948f9..c6963bad21e628bdb4a038fe4ecfc2cd8e022fb0 100644
--- a/inc/Platform/x86/ECSS_Configuration.hpp
+++ b/inc/Platform/x86/ECSS_Configuration.hpp
@@ -10,7 +10,7 @@
  */
 
 /**
- * @defgroup ServiceDefinitions Service compilation switches
+ * @name ServiceDefinitions Service compilation switches
  * These preprocessor defines control whether the compilation of each ECSS service is enabled. By not defining one of
  * those, the service will not be compiled, and no RAM or ROM will be spent storing it.
  *
diff --git a/inc/Time/Time.hpp b/inc/Time/Time.hpp
index b7c803339ad5bcebb1c566bb948a7f56abd84470..3e2665819e902c98b8f060dfe67879792630a2ab 100644
--- a/inc/Time/Time.hpp
+++ b/inc/Time/Time.hpp
@@ -1,6 +1,7 @@
 #ifndef ECSS_TIMEHPP
 #define ECSS_TIMEHPP
 
+#include <chrono>
 #include <cstdint>
 #include "ErrorHandler.hpp"
 #include "etl/String.hpp"
@@ -79,15 +80,15 @@ namespace Time {
 	/**
 	 * Number of bytes used for the basic time units of the CUC header for this mission
 	 */
-	inline constexpr uint8_t CUCSecondsBytes = 2;
+	inline constexpr uint8_t CUCSecondsBytes = 4;
 
 	/**
 	 * Number of bytes used for the fractional time units of the CUC header for this mission
 	 */
-	inline constexpr uint8_t CUCFractionalBytes = 2;
+	inline constexpr uint8_t CUCFractionalBytes = 1;
 
 	/**
-	 * The system epoch (clock measurement starting time)
+	 * The system epoch (clock measurement starting time).
 	 * All timestamps emitted by the ECSS services will show the elapsed time (seconds, days etc.) from this epoch.
  	*/
 	inline constexpr struct {
@@ -238,7 +239,6 @@ namespace Time {
 	 */
 	template <typename T, int secondsBytes, int fractionalBytes>
 	inline constexpr T buildCUCHeader() {
-		// TODO: Gitlab issue #106
 		static_assert((secondsBytes + fractionalBytes) <= 8, "Complete arbitrary precision not supported");
 		// cppcheck-suppress syntaxError
 		// cppcheck-suppress redundantCondition
@@ -315,6 +315,25 @@ namespace Time {
 		return time1.elapsed100msTicks >= time2.elapsed100msTicks;
 	}
 
+	/**
+	 * is_duration definition to check if a variable is std::chrono::duration
+	 */
+	template <typename T>
+	struct is_duration
+	    : std::false_type {};
+
+	/**
+	 * is_duration definition to check if a variable is std::chrono::duration
+	 */
+	template <typename Rep, typename Period>
+	struct is_duration<std::chrono::duration<Rep, Period>>
+	    : std::true_type {};
+
+	/**
+	 * True if T is std::chrono::duration, false if not
+	 */
+	template <class T>
+	inline constexpr bool is_duration_v = is_duration<T>::value;
 } // namespace Time
 
 #endif
diff --git a/inc/Time/TimeStamp.hpp b/inc/Time/TimeStamp.hpp
index 52fade25ad8dd9ff8c8fd078bf6f940e6d8b3bc7..52880509fc3fe11a09bf34ac3c0748929d8ed719 100644
--- a/inc/Time/TimeStamp.hpp
+++ b/inc/Time/TimeStamp.hpp
@@ -1,37 +1,96 @@
 #ifndef ECSS_SERVICES_TIME_HPP
 #define ECSS_SERVICES_TIME_HPP
 
-#include <cstdint>
 #include <algorithm>
-#include "macros.hpp"
+#include <chrono>
+#include <cstdint>
 #include <etl/array.h>
 #include "Time.hpp"
 #include "UTCTimestamp.hpp"
+#include "macros.hpp"
 
 /**
  * A class that represents an instant in time, with convenient conversion
  * to and from usual time and date representations
  *
+ * This class is compatible with the CUC (Unsegmented Time Code) format defined in CCSDS 301.0-B-4. It allows specifying:
+ * - Different amount of bytes for the basic time unit
+ * - Different amount of bytes for the fractional time unit
+ * - Different basic time units
+ *
+ * The timestamp is defined in relation to a user-defined epoch, set in @ref Time::Epoch.
+ *
+ * @section baseunit Setting the base time unit
+ * By default, this class measures time in the order of **seconds**. Binary fractions of a second can be specified by increasing the FractionBytes.
+ * However, the user can change the base time unit by setting the @p Num and @p Denom template parameters.
+ *
+ * The base time unit (or period) is then represented by the following:
+ * \f[
+ * \text{time unit} = \frac{Num}{Denom} \cdot \text{second}
+ * \f]
+ *
  * @note
  * This class uses internally TAI time, and handles UTC leap seconds at conversion to and
  * from UTC time system.
  *
+ * @tparam BaseBytes The number of bytes used for the basic time units. This essentially defines the maximum duration from Epoch that this timestamp can represent.
+ * @tparam FractionBytes The number of bytes used for the fraction of one basic time unit. This essentially defines the precision of the timestamp.
+ * @tparam Num The numerator of the base type ratio (see @ref baseunit)
+ * @tparam Denom The numerator of the base type ratio (see @ref baseunit)
+ *
  * @ingroup Time
  * @author Baptiste Fournier
+ * @author Konstantinos Kanavouras
  * @see [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
  */
-template <uint8_t secondsBytes, uint8_t fractionalBytes>
+template <uint8_t BaseBytes, uint8_t FractionBytes = 0, int Num = 1, int Denom = 1>
 class TimeStamp {
+public:
+	/**
+	 * The period of the base type, in relation to the second
+	 *
+	 * This type represents the base type of the timestamp.
+	 *
+	 * A ratio of `<1, 1>` (or 1/1) means that this timestamp represents seconds. A ratio of `<60, 1>` (or 60/1) means
+	 * that this class represents 60s of seconds, or minutes. A ratio of `<1, 1000>` (or 1/1000) means that this class
+	 * represents 1000ths of seconds, or milliseconds.
+	 *
+	 * This type has essentially the same meaning of `Rep` in [std::chrono::duration](https://en.cppreference.com/w/cpp/chrono/duration).
+	 *
+	 * @note std::ratio will simplify the fractions numerator and denominator
+	 */
+	using Ratio = std::ratio<Num, Denom>;
+
 private:
-	static_assert(secondsBytes + fractionalBytes <= 8,
+	static_assert(BaseBytes + FractionBytes <= 8,
 	              "Currently, this class is not suitable for storage on internal counter larger than uint64_t");
-	typedef typename std::conditional<(secondsBytes < 4 && fractionalBytes < 3), uint8_t, uint16_t>::type CUCHeader_t;
-	typedef typename std::conditional<(secondsBytes + fractionalBytes < 4), uint32_t, uint64_t>::type TAICounter_t;
+	using CUCHeader_t = typename std::conditional<(BaseBytes < 4 && FractionBytes < 3), uint8_t, uint16_t>::type;
+	using TAICounter_t = typename std::conditional<(BaseBytes + FractionBytes <= 4), uint32_t, uint64_t>::type;
+
+	/**
+	 * The period of the internal counter
+	 *
+	 * Same as @ref Ratio, but instead of representing the Base bytes, it represents the entire value held by @ref taiCounter.
+	 */
+	using RawRatio = std::ratio<Num, Denom * 1UL << (8 * FractionBytes)>;
+
+	/**
+	 * An std::chrono::duration representation of the base type (without the fractional part)
+	 */
+	using BaseDuration = std::chrono::duration<TAICounter_t, Ratio>;
+
+	/**
+	 * An std::chrono::duration representation of the complete @ref taiCounter (including the fractional part)
+	 */
+	using RawDuration = std::chrono::duration<TAICounter_t, RawRatio>;
+
+	template <uint8_t, uint8_t, int, int>
+	friend class TimeStamp;
 
 	/**
 	 * Integer counter of time units since the @ref Time::Epoch. This number essentially represents the timestamp.
 	 *
-	 * The unit represented by this variable depends on `secondsBytes` and `fractionalBytes`. The fractional
+	 * The unit represented by this variable depends on `BaseBytes` and `FractionBytes`. The fractional
 	 * part is included as the least significant bits of this variable, and the base part follows.
 	 */
 	TAICounter_t taiCounter;
@@ -39,13 +98,17 @@ private:
 	/**
 	 * The constant header ("P-value") of the timestamp, if needed to be attached to any message
 	 */
-	static constexpr CUCHeader_t CUCHeader = Time::buildCUCHeader<CUCHeader_t, secondsBytes, fractionalBytes>();
+	static constexpr CUCHeader_t CUCHeader = Time::buildCUCHeader<CUCHeader_t, BaseBytes, FractionBytes>();
+
+	/**
+	 * The maximum value of the base type (seconds, larger or smaller) that can fit in @ref taiCounter
+	 */
+	static constexpr uint64_t MaxBase = (BaseBytes == 8) ? std::numeric_limits<uint64_t>::max() : (1UL << 8 * BaseBytes) - 1;
 
 	/**
-	 * The maximum value that can fit in @ref taiCounter, or the maximum number of seconds since epoch that can be
-	 * represented in this base class
+	 * The maximum number of seconds since epoch that can be represented in this class
 	 */
-	static constexpr uint64_t maxSecondCounterValue = (uint64_t{1U} << (8U * secondsBytes)) - 1;
+	static constexpr uint64_t MaxSeconds = std::chrono::duration_cast<std::chrono::duration<uint64_t>>(BaseDuration(MaxBase)).count();
 
 	/**
 	 * Returns whether the amount of `seconds` can be represented by this TimeStamp.
@@ -79,8 +142,7 @@ public:
 	/**
 	 * Initialize the TimeStamp from the bytes of a CUC time stamp
 	 *
-	 * @param timestamp A complete CUC timestamp including header, of the maximum possible size, zero padded to the
-	 * right
+	 * @param timestamp A complete CUC timestamp including header, of the maximum possible size, zero padded to the right
 	 */
 	explicit TimeStamp(etl::array<uint8_t, Time::CUCTimestampMaximumSize> timestamp);
 
@@ -91,6 +153,26 @@ public:
 	 */
 	explicit TimeStamp(const UTCTimestamp& timestamp);
 
+	/**
+	 * Convert a TimeStamp to a TimeStamp with different parameters
+	 *
+	 * This constructor will convert based on the number of bytes, and base units
+	 *
+	 * @note Internally uses double-precision floating point to allow for arbitrary ratios
+	 */
+	template <uint8_t BaseBytesIn, uint8_t FractionBytesIn, int NumIn = 1, int DenomIn = 1>
+	explicit TimeStamp(TimeStamp<BaseBytesIn, FractionBytesIn, NumIn, DenomIn>);
+
+	/**
+	 * Convert an [std::chrono::duration](https://en.cppreference.com/w/cpp/chrono/duration) representing seconds from @ref Time::Epoch
+	 * to a timestamp
+	 *
+	 * @warning This function does not perform overflow calculations. It is up to the user to ensure that the types are
+	 * compatible so that no overflow occurs.
+	 */
+	template <class Duration, typename = std::enable_if_t<Time::is_duration_v<Duration>>>
+	explicit TimeStamp(Duration duration);
+
 	/**
 	 * Get the representation as seconds from epoch in TAI
 	 *
@@ -110,19 +192,26 @@ public:
 	 * Get the representation as seconds from epoch in TAI, for a floating-point representation.
 	 * For an integer result, see the overloaded @ref asTAIseconds function.
 	 *
-	 * @todo Implement integer seconds in this function
 	 * @tparam T The return type of the seconds (float or double).
 	 * @return The seconds elapsed in TAI since @ref Time::Epoch
 	 */
 	template <typename T>
 	T asTAIseconds();
 
+	/**
+	 * Converts a TimeStamp to a duration of seconds since the @ref Time::Epoch.
+	 *
+	 * @warning This function does not perform overflow calculations. It is up to the user to ensure that the types are compatible so that no overflow occurs.
+	 */
+	template <class Duration = std::chrono::seconds>
+	Duration asDuration();
+
 	/**
 	 * Get the representation as CUC formatted bytes
 	 *
 	 * @return The TimeStamp, represented in the CCSDS CUC format
 	 */
-	etl::array<uint8_t, Time::CUCTimestampMaximumSize> toCUCtimestamp();
+	etl::array<uint8_t, Time::CUCTimestampMaximumSize> formatAsCUC();
 
 	/**
 	 * Get the representation as a UTC timestamp
@@ -132,37 +221,43 @@ public:
 	UTCTimestamp toUTCtimestamp();
 
 	/**
-	 * Compare two timestamps.
-	 *
-	 * @param timestamp the date that will be compared with the pointer `this`
-	 * @return true if the condition is satisfied
+	 * @name Comparison operators between timestamps
+	 * @{
 	 */
-	bool operator<(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const {
-		return taiCounter < timestamp.taiCounter;
+	template <class OtherTimestamp>
+	bool operator<(const OtherTimestamp& timestamp) const {
+		return RawDuration(taiCounter) < typename OtherTimestamp::RawDuration(timestamp.taiCounter);
 	}
 
-	bool operator>(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const {
-		return taiCounter > timestamp.taiCounter;
+	template <class OtherTimestamp>
+	bool operator>(const OtherTimestamp& timestamp) const {
+		return RawDuration(taiCounter) > typename OtherTimestamp::RawDuration(timestamp.taiCounter);
 	}
 
-	bool operator==(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const {
-		return taiCounter == timestamp.taiCounter;
+	template <class OtherTimestamp>
+	bool operator==(const OtherTimestamp& timestamp) const {
+		return RawDuration(taiCounter) == typename OtherTimestamp::RawDuration(timestamp.taiCounter);
 	}
 
-	bool operator!=(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const {
-		return taiCounter != timestamp.taiCounter;
+	template <class OtherTimestamp>
+	bool operator!=(const OtherTimestamp& timestamp) const {
+		return RawDuration(taiCounter) != typename OtherTimestamp::RawDuration(timestamp.taiCounter);
 	}
 
-	bool operator<=(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const {
-		return taiCounter <= timestamp.taiCounter;
+	template <class OtherTimestamp>
+	bool operator<=(const OtherTimestamp& timestamp) const {
+		return RawDuration(taiCounter) <= typename OtherTimestamp::RawDuration(timestamp.taiCounter);
 	}
 
-	bool operator>=(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const {
-		return taiCounter >= timestamp.taiCounter;
+	template <class OtherTimestamp>
+	bool operator>=(const OtherTimestamp& timestamp) const {
+		return RawDuration(taiCounter) >= typename OtherTimestamp::RawDuration(timestamp.taiCounter);
 	}
+	/**
+	 * @}
+	 */
 };
 
 #include "TimeStamp.tpp"
-typedef TimeStamp<Time::CUCSecondsBytes, Time::CUCFractionalBytes> AcubeSATTimeStamp_t;
 
 #endif
diff --git a/inc/Time/TimeStamp.tpp b/inc/Time/TimeStamp.tpp
index 34edf5a2c291153b1a38778ebc0462ec661d9fd6..e4b33b4f40fe30f1fd82269ffedf4974386869f2 100644
--- a/inc/Time/TimeStamp.tpp
+++ b/inc/Time/TimeStamp.tpp
@@ -1,46 +1,48 @@
-template <uint8_t secondsBytes, uint8_t fractionalBytes>
-constexpr bool TimeStamp<secondsBytes, fractionalBytes>::areSecondsValid(TimeStamp::TAICounter_t seconds) {
-	return seconds < maxSecondCounterValue;
+#include <cmath>
+#include "TimeStamp.hpp"
+
+template <uint8_t BaseBytes, uint8_t FractionBytes, int Num, int Denom>
+constexpr bool TimeStamp<BaseBytes, FractionBytes, Num, Denom>::areSecondsValid(TimeStamp::TAICounter_t seconds) {
+	return seconds <= MaxSeconds;
 }
 
-template <uint8_t secondsBytes, uint8_t fractionalBytes>
-TimeStamp<secondsBytes, fractionalBytes>::TimeStamp(uint64_t taiSecondsFromEpoch) {
-	ASSERT_INTERNAL(areSecondsValid((taiSecondsFromEpoch)), ErrorHandler::InternalErrorType::InvalidTimeStampInput);
+template <uint8_t BaseBytes, uint8_t FractionBytes, int Num, int Denom>
+TimeStamp<BaseBytes, FractionBytes, Num, Denom>::TimeStamp(uint64_t taiSecondsFromEpoch) {
+	ASSERT_INTERNAL(areSecondsValid((taiSecondsFromEpoch)), ErrorHandler::InternalErrorType::TimeStampOutOfBounds);
+
+	using FromDuration = std::chrono::duration<uint64_t>;
+	const auto duration = FromDuration(taiSecondsFromEpoch);
 
-	taiCounter = static_cast<TAICounter_t>(taiSecondsFromEpoch) << 8 * fractionalBytes;
+	taiCounter = std::chrono::duration_cast<RawDuration>(duration).count();
 }
 
-template <uint8_t secondsBytes, uint8_t fractionalBytes>
-TimeStamp<secondsBytes, fractionalBytes>::TimeStamp(Time::CustomCUC_t customCUCTimestamp) {
-	ASSERT_INTERNAL(areSecondsValid((customCUCTimestamp.elapsed100msTicks / 10)),
-	                ErrorHandler::InternalErrorType::InvalidTimeStampInput);
-	taiCounter = static_cast<TAICounter_t>(customCUCTimestamp.elapsed100msTicks / 10);
-	if (fractionalBytes > 0) {
-		TAICounter_t fractionalPart = static_cast<TAICounter_t>(customCUCTimestamp.elapsed100msTicks) - 10 * taiCounter;
-		taiCounter = taiCounter << 8;
-		taiCounter += fractionalPart * 256 / 10;
-		taiCounter = taiCounter << 8 * (fractionalBytes - 1);
-	}
+template <uint8_t BaseBytes, uint8_t FractionBytes, int Num, int Denom>
+TimeStamp<BaseBytes, FractionBytes, Num, Denom>::TimeStamp(Time::CustomCUC_t customCUCTimestamp) {
+	//TODO Remove CustomCUC_t class
+	TimeStamp<8, 0, 1, 10> input;
+	input.taiCounter = customCUCTimestamp.elapsed100msTicks;
+
+	new (this) TimeStamp<BaseBytes, FractionBytes, Num, Denom>(input);
 }
 
-template <uint8_t secondsCounter, uint8_t fractionalBytes>
-TimeStamp<secondsCounter, fractionalBytes>::TimeStamp(etl::array<uint8_t, Time::CUCTimestampMaximumSize> timestamp) {
+template <uint8_t BaseBytes, uint8_t FractionBytes, int Num, int Denom>
+TimeStamp<BaseBytes, FractionBytes, Num, Denom>::TimeStamp(etl::array<uint8_t, Time::CUCTimestampMaximumSize> timestamp) {
 	// process header
 	uint8_t headerSize = 1;
 	if ((timestamp[0] & 0b10000000U) != 0) {
 		headerSize = 2;
 	}
 
-	uint8_t inputSecondsBytes = ((timestamp[0] & 0b00001100U) >> 2U) + 1U;
-	uint8_t inputFractionalBytes = (timestamp[0] & 0b00000011U) >> 0U;
+	uint8_t inputBaseBytes = ((timestamp[0] & 0b00001100U) >> 2U) + 1U;
+	uint8_t inputFractionBytes = (timestamp[0] & 0b00000011U) >> 0U;
 
 	if (headerSize == 2) {
-		inputSecondsBytes += (timestamp[1] & 0b01100000U) >> 5U;
-		inputFractionalBytes += (timestamp[1] & 0b00011100U) >> 2U;
+		inputBaseBytes += (timestamp[1] & 0b01100000U) >> 5U;
+		inputFractionBytes += (timestamp[1] & 0b00011100U) >> 2U;
 	}
 
 	// check input validity (useless bytes set to 0)
-	for (int i = headerSize + inputSecondsBytes + inputFractionalBytes; i < Time::CUCTimestampMaximumSize; i++) {
+	for (int i = headerSize + inputBaseBytes + inputFractionBytes; i < Time::CUCTimestampMaximumSize; i++) {
 		if (timestamp[i] != 0) {
 			ErrorHandler::reportInternalError(ErrorHandler::InternalErrorType::InvalidTimeStampInput);
 			break;
@@ -48,78 +50,89 @@ TimeStamp<secondsCounter, fractionalBytes>::TimeStamp(etl::array<uint8_t, Time::
 	}
 
 	// do checks wrt template precision parameters
-	ASSERT_INTERNAL(inputSecondsBytes <= secondsCounter, ErrorHandler::InternalErrorType::InvalidTimeStampInput);
-	ASSERT_INTERNAL(inputFractionalBytes <= fractionalBytes, ErrorHandler::InternalErrorType::InvalidTimeStampInput);
+	ASSERT_INTERNAL(inputBaseBytes <= BaseBytes, ErrorHandler::InternalErrorType::InvalidTimeStampInput);
+	ASSERT_INTERNAL(inputFractionBytes <= FractionBytes, ErrorHandler::InternalErrorType::InvalidTimeStampInput);
 
 	// put timestamp into internal counter
 	taiCounter = 0;
 	// add seconds until run out of bytes on input array
-	for (auto i = 0; i < inputSecondsBytes + inputFractionalBytes; i++) {
+	for (auto i = 0; i < inputBaseBytes + inputFractionBytes; i++) {
 		taiCounter = taiCounter << 8;
 		taiCounter += timestamp[headerSize + i];
 	}
 	// pad rightmost bytes to full length
-	taiCounter = taiCounter << 8 * (fractionalBytes - inputFractionalBytes);
+	taiCounter = taiCounter << 8 * (FractionBytes - inputFractionBytes);
 }
 
-template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes>
-TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::TimeStamp(const UTCTimestamp& timestamp) {
+template <uint8_t BaseBytes, uint8_t FractionBytes, int Num, int Denom>
+TimeStamp<BaseBytes, FractionBytes, Num, Denom>::TimeStamp(const UTCTimestamp& timestamp) {
 	TAICounter_t seconds = 0;
+
+	/**
+	 * Add to the seconds variable, with an overflow check
+	 */
+	auto secondsAdd = [&seconds](TAICounter_t value) {
+		seconds += value;
+		if (seconds < value) {
+			ErrorHandler::reportInternalError(ErrorHandler::TimeStampOutOfBounds);
+		}
+	};
+
 	for (int year = Time::Epoch.year; year < timestamp.year; ++year) {
-		seconds += (Time::isLeapYear(year) ? 366 : 365) * Time::SecondsPerDay;
+		secondsAdd((Time::isLeapYear(year) ? 366 : 365) * Time::SecondsPerDay);
 	}
 	for (int month = Time::Epoch.month; month < timestamp.month; ++month) {
-		seconds += Time::DaysOfMonth[month - 1] * Time::SecondsPerDay;
+		secondsAdd(Time::DaysOfMonth[month - 1] * Time::SecondsPerDay);
 		if ((month == 2U) && Time::isLeapYear(timestamp.year)) {
-			seconds += Time::SecondsPerDay;
+			secondsAdd(Time::SecondsPerDay);
 		}
 	}
-	seconds += (timestamp.day - Time::Epoch.day) * Time::SecondsPerDay;
-	seconds += timestamp.hour * Time::SecondsPerHour;
-	seconds += timestamp.minute * Time::SecondsPerMinute;
-	seconds += timestamp.second;
-	// TODO: Add check that `seconds` is within bounds (?)
-	taiCounter = static_cast<TAICounter_t>(seconds) << 8 * fractional_counter_bytes;
+
+	secondsAdd((timestamp.day - Time::Epoch.day) * Time::SecondsPerDay);
+	secondsAdd(timestamp.hour * Time::SecondsPerHour);
+	secondsAdd(timestamp.minute * Time::SecondsPerMinute);
+	secondsAdd(timestamp.second);
+
+	ASSERT_INTERNAL(areSecondsValid(seconds), ErrorHandler::TimeStampOutOfBounds);
+
+	taiCounter = static_cast<TAICounter_t>(seconds) << (8 * FractionBytes);
 }
 
-template <uint8_t secondsBytes, uint8_t fractionalBytes>
-typename TimeStamp<secondsBytes, fractionalBytes>::TAICounter_t
-TimeStamp<secondsBytes, fractionalBytes>::asTAIseconds() {
-	return taiCounter >> (8 * fractionalBytes);
+template <uint8_t BaseBytes, uint8_t FractionBytes, int Num, int Denom>
+typename TimeStamp<BaseBytes, FractionBytes, Num, Denom>::TAICounter_t
+TimeStamp<BaseBytes, FractionBytes, Num, Denom>::asTAIseconds() {
+	const auto duration = RawDuration(taiCounter);
+	using ToDuration = std::chrono::duration<TAICounter_t>;
+
+	return std::chrono::duration_cast<ToDuration>(duration).count();
 }
 
-template <uint8_t secondsBytes, uint8_t fractionalBytes>
-Time::CustomCUC_t TimeStamp<secondsBytes, fractionalBytes>::asCustomCUCTimestamp() {
-	TAICounter_t temp = taiCounter;
-	Time::CustomCUC_t return_s = {0};
-	if (fractionalBytes > 0) {
-		temp = temp >> 8 * (fractionalBytes - 1);
-		return_s.elapsed100msTicks += temp * 10 / 256;
-	} else {
-		return_s.elapsed100msTicks += temp * 10;
-	}
-	return return_s;
+template <uint8_t BaseBytes, uint8_t FractionBytes, int Num, int Denom>
+Time::CustomCUC_t TimeStamp<BaseBytes, FractionBytes, Num, Denom>::asCustomCUCTimestamp() {
+	//TODO: Remove CustomCUC_t class
+	TimeStamp<8, 0, 1, 10> converted(*this);
+	return {converted.taiCounter};
 }
 
-template <uint8_t secondsBytes, uint8_t fractionalBytes>
+template <uint8_t BaseBytes, uint8_t FractionBytes, int Num, int Denom>
 template <typename T>
-T TimeStamp<secondsBytes, fractionalBytes>::asTAIseconds() {
+T TimeStamp<BaseBytes, FractionBytes, Num, Denom>::asTAIseconds() {
 	static_assert(std::is_floating_point_v<T>, "TimeStamp::asTAIseconds() only accepts numeric types.");
-	static_assert(std::numeric_limits<T>::max() >= maxSecondCounterValue);
+	static_assert(std::numeric_limits<T>::max() >= MaxSeconds);
 
-	TAICounter_t decimalPart = taiCounter >> (8 * fractionalBytes);
+	TAICounter_t decimalPart = taiCounter >> (8 * FractionBytes);
 
-	T fractionalPart = taiCounter - (decimalPart << (8 * fractionalBytes));
-	T fractionalPartMax = (1U << (8U * fractionalBytes)) - 1U;
+	T fractionalPart = taiCounter - (decimalPart << (8 * FractionBytes));
+	T fractionalPartMax = (1U << (8U * FractionBytes)) - 1U;
 
 	return decimalPart + fractionalPart / fractionalPartMax;
 }
 
-template <uint8_t secondsBytes, uint8_t fractionalBytes>
-etl::array<uint8_t, Time::CUCTimestampMaximumSize> TimeStamp<secondsBytes, fractionalBytes>::toCUCtimestamp() {
+template <uint8_t BaseBytes, uint8_t FractionBytes, int Num, int Denom>
+etl::array<uint8_t, Time::CUCTimestampMaximumSize> TimeStamp<BaseBytes, FractionBytes, Num, Denom>::formatAsCUC() {
 	etl::array<uint8_t, Time::CUCTimestampMaximumSize> returnArray = {0};
 
-	static constexpr uint8_t headerBytes = (secondsBytes < 4 && fractionalBytes < 3) ? 1 : 2;
+	static constexpr uint8_t headerBytes = (BaseBytes < 4 && FractionBytes < 3) ? 1 : 2;
 
 	if (headerBytes == 1) {
 		returnArray[0] = static_cast<uint8_t>(CUCHeader);
@@ -128,16 +141,16 @@ etl::array<uint8_t, Time::CUCTimestampMaximumSize> TimeStamp<secondsBytes, fract
 		returnArray[0] = static_cast<uint8_t>(CUCHeader >> 8);
 	}
 
-	for (auto byte = 0; byte < secondsBytes + fractionalBytes; byte++) {
-		uint8_t taiCounterIndex = 8 * (secondsBytes + fractionalBytes - byte - 1);
+	for (auto byte = 0; byte < BaseBytes + FractionBytes; byte++) {
+		uint8_t taiCounterIndex = 8 * (BaseBytes + FractionBytes - byte - 1);
 		returnArray[headerBytes + byte] = taiCounter >> taiCounterIndex;
 	}
 
 	return returnArray;
 }
 
-template <uint8_t secondsBytes, uint8_t fractionalBytes>
-UTCTimestamp TimeStamp<secondsBytes, fractionalBytes>::toUTCtimestamp() {
+template <uint8_t BaseBytes, uint8_t FractionBytes, int Num, int Denom>
+UTCTimestamp TimeStamp<BaseBytes, FractionBytes, Num, Denom>::toUTCtimestamp() {
 	using namespace Time;
 
 	uint32_t totalSeconds = asTAIseconds();
@@ -185,3 +198,38 @@ UTCTimestamp TimeStamp<secondsBytes, fractionalBytes>::toUTCtimestamp() {
 
 	return {yearUTC, monthUTC, dayUTC, hour, minute, second};
 }
+template <uint8_t BaseBytes, uint8_t FractionBytes, int Num, int Denom>
+template <uint8_t BaseBytesIn, uint8_t FractionBytesIn, int NumIn, int DenomIn>
+TimeStamp<BaseBytes, FractionBytes, Num, Denom>::TimeStamp(TimeStamp<BaseBytesIn, FractionBytesIn, NumIn, DenomIn> input) {
+	if constexpr (std::is_same_v<decltype(*this), decltype(input)>) {
+		taiCounter = input.taiCounter;
+		return;
+	}
+
+	constexpr double InputRatio = static_cast<double>(NumIn) / DenomIn;
+	constexpr double OutputRatio = static_cast<double>(Num) / Denom;
+
+	double inputSeconds = input.taiCounter / static_cast<double>(1 << (8 * FractionBytesIn));
+	inputSeconds *= InputRatio;
+
+	ErrorHandler::assertInternal(inputSeconds <= MaxSeconds, ErrorHandler::TimeStampOutOfBounds);
+
+	double output = inputSeconds / OutputRatio * (1UL << (8 * FractionBytes));
+
+	taiCounter = static_cast<TAICounter_t>(round(output));
+}
+
+template <uint8_t BaseBytes, uint8_t FractionBytes, int Num, int Denom>
+template <class Duration>
+Duration TimeStamp<BaseBytes, FractionBytes, Num, Denom>::asDuration() {
+	auto duration = RawDuration(taiCounter);
+
+	return std::chrono::duration_cast<Duration>(duration);
+}
+
+template <uint8_t BaseBytes, uint8_t FractionBytes, int Num, int Denom>
+template <class Duration, typename>
+TimeStamp<BaseBytes, FractionBytes, Num, Denom>::TimeStamp(Duration duration) {
+	auto outputDuration = std::chrono::duration_cast<RawDuration>(duration);
+	taiCounter = outputDuration.count();
+}
diff --git a/test/Services/ParameterStatisticsServiceTests.cpp b/test/Services/ParameterStatisticsServiceTests.cpp
index 74a0e81b0c7fc17f31fe5c70b3e1c5a81d80374a..3ec7031866804325b5b35c9b3a8c0f6bf12d761b 100644
--- a/test/Services/ParameterStatisticsServiceTests.cpp
+++ b/test/Services/ParameterStatisticsServiceTests.cpp
@@ -27,10 +27,6 @@ void initializeStatistics(uint16_t interval1, uint16_t interval2) {
 	Services.parameterStatistics.statisticsMap.insert({id2, stat2});
 }
 
-void resetSystem() {
-	Services.parameterStatistics.statisticsMap.clear();
-}
-
 TEST_CASE("Reporting of statistics") {
 	SECTION("Report statistics, with auto statistic reset disabled with TC") {
 		initializeStatistics(6, 7);
@@ -97,9 +93,7 @@ TEST_CASE("Reporting of statistics") {
 		CHECK(Services.parameterStatistics.statisticsMap[7].statisticsAreInitialized());
 	}
 
-	resetSystem();
 	ServiceTests::reset();
-	Services.reset();
 
 	SECTION("Report statistics, with auto statistic reset disabled without TC") {
 		initializeStatistics(6, 7);
@@ -157,11 +151,7 @@ TEST_CASE("Reporting of statistics") {
 
 		CHECK(Services.parameterStatistics.statisticsMap[5].statisticsAreInitialized());
 		CHECK(Services.parameterStatistics.statisticsMap[7].statisticsAreInitialized());
-
 	}
-	resetSystem();
-	ServiceTests::reset();
-	Services.reset();
 }
 
 TEST_CASE("Resetting the parameter statistics") {
@@ -178,9 +168,7 @@ TEST_CASE("Resetting the parameter statistics") {
 		CHECK(Services.parameterStatistics.statisticsMap[5].statisticsAreInitialized());
 		CHECK(Services.parameterStatistics.statisticsMap[7].statisticsAreInitialized());
 
-		resetSystem();
 		ServiceTests::reset();
-		Services.reset();
 	}
 
 	SECTION("Reset without TC") {
@@ -194,9 +182,7 @@ TEST_CASE("Resetting the parameter statistics") {
 		CHECK(Services.parameterStatistics.statisticsMap[5].statisticsAreInitialized());
 		CHECK(Services.parameterStatistics.statisticsMap[7].statisticsAreInitialized());
 
-		resetSystem();
 		ServiceTests::reset();
-		Services.reset();
 	}
 }
 
@@ -231,9 +217,7 @@ TEST_CASE("Enable the periodic reporting of statistics") {
 		CHECK(Services.parameterStatistics.getPeriodicReportingStatus() == false);
 		CHECK(Services.parameterStatistics.getReportingIntervalMs() == 6);
 
-		resetSystem();
 		ServiceTests::reset();
-		Services.reset();
 	}
 }
 
@@ -248,9 +232,7 @@ TEST_CASE("Disabling the periodic reporting of statistics") {
 		MessageParser::execute(request);
 		REQUIRE(Services.parameterStatistics.getPeriodicReportingStatus() == false);
 
-		resetSystem();
 		ServiceTests::reset();
-		Services.reset();
 	}
 }
 
@@ -280,9 +262,7 @@ TEST_CASE("Add/Update statistics definitions") {
 		CHECK(Services.parameterStatistics.statisticsMap.size() == 3);
 		CHECK(Services.parameterStatistics.statisticsMap[0].selfSamplingInterval == 1400);
 
-		resetSystem();
 		ServiceTests::reset();
-		Services.reset();
 	}
 
 	SECTION("Add new statistic definition") {
@@ -306,9 +286,7 @@ TEST_CASE("Add/Update statistics definitions") {
 		CHECK(Services.parameterStatistics.statisticsMap.size() == 3);
 		CHECK(Services.parameterStatistics.statisticsMap[1].selfSamplingInterval == 3200);
 
-		resetSystem();
 		ServiceTests::reset();
-		Services.reset();
 	}
 
 	SECTION("All possible invalid requests combined with add/update") {
@@ -362,9 +340,7 @@ TEST_CASE("Add/Update statistics definitions") {
 		CHECK(Services.parameterStatistics.statisticsMap[0].selfSamplingInterval == 14000);
 		CHECK(Services.parameterStatistics.statisticsMap[1].selfSamplingInterval == 32000);
 
-		resetSystem();
 		ServiceTests::reset();
-		Services.reset();
 	}
 }
 
@@ -410,12 +386,9 @@ TEST_CASE("Delete statistics definitions") {
 		MessageParser::execute(request);
 
 		CHECK(Services.parameterStatistics.getPeriodicReportingStatus() == false);
-		CHECK(ServiceTests::countThrownErrors(ErrorHandler::GetNonExistingParameter) == 1);
 		CHECK(Services.parameterStatistics.statisticsMap.empty());
 
-		resetSystem();
 		ServiceTests::reset();
-		Services.reset();
 	}
 }
 
@@ -441,8 +414,6 @@ TEST_CASE("Parameter statistics definition report") {
 		CHECK(report.readUint16() == 7);
 		CHECK(report.readUint16() == 0);
 
-		resetSystem();
 		ServiceTests::reset();
-		Services.reset();
 	}
 }
diff --git a/test/Services/ServiceTests.hpp b/test/Services/ServiceTests.hpp
index d5b76b882b1f788b7da4267f5ab4d3f4841f5168..f45726d85f89bf15afa216d3fdadd684dec47239 100644
--- a/test/Services/ServiceTests.hpp
+++ b/test/Services/ServiceTests.hpp
@@ -84,13 +84,17 @@ public:
 		return count() == 1;
 	}
 
+	static void resetErrors() {
+		queuedMessages.clear();
+		thrownErrors.clear();
+		expectingErrors = false;
+	}
+
 	/**
 	 * Reset the testing environment, starting from zero for all parameters
 	 */
 	static void reset() {
-		queuedMessages.clear();
-		thrownErrors.clear();
-		expectingErrors = false;
+		resetErrors();
 
 		Services.reset();
 	}
@@ -150,6 +154,19 @@ public:
 
 		return thrownErrors.count(std::make_pair(errorSource, errorType));
 	}
+
+	/**
+	 * Get the list of all thrown errors
+	 */
+	static std::vector<std::pair<ErrorHandler::ErrorSource, uint16_t>> getThrownErrors() {
+		std::vector<std::pair<ErrorHandler::ErrorSource, uint16_t>> errors;
+
+		for (auto error : thrownErrors) {
+			errors.push_back(error.first);
+		}
+
+		return errors;
+	}
 };
 
 #endif // ECSS_SERVICES_TESTS_SERVICES_SERVICETESTS_HPP
diff --git a/test/TestPlatform.cpp b/test/TestPlatform.cpp
index 29a222854d4f48ccd437b8b64dfc468ed5fd032f..a2a2d80f1fb09d8aed88242b88a8e35f516d210d 100644
--- a/test/TestPlatform.cpp
+++ b/test/TestPlatform.cpp
@@ -5,6 +5,7 @@
 #include <Message.hpp>
 #include <Service.hpp>
 #include <catch2/catch_all.hpp>
+#include <cxxabi.h>
 #include "Helpers/Parameter.hpp"
 #include "Helpers/TimeGetter.hpp"
 #include "Parameters/PlatformParameters.hpp"
@@ -51,10 +52,18 @@ void ErrorHandler::logError(const Message& message, ErrorType errorType) {
 template <typename ErrorType>
 void ErrorHandler::logError(ErrorType errorType) {
 	ServiceTests::addError(ErrorHandler::findErrorSource(errorType), errorType);
+
+	auto errorCategory = abi::__cxa_demangle(typeid(ErrorType).name(), nullptr, nullptr, nullptr);
+	auto errorNumber = std::underlying_type_t<ErrorType>(errorType);
+
+	LOG_ERROR << "Error " << errorCategory << " with number " << errorNumber;
 }
 
 void Logger::log(Logger::LogLevel level, etl::istring& message) {
-	// Logs while testing are completely ignored
+	// Logs while testing are passed on to Catch2, if they are important enough
+	if (level >= Logger::warning) {
+		UNSCOPED_INFO(message.c_str());
+	}
 }
 
 struct ServiceTestsListener : Catch::EventListenerBase {
@@ -65,9 +74,14 @@ struct ServiceTestsListener : Catch::EventListenerBase {
 		if (not ServiceTests::isExpectingErrors()) {
 			// An Error was thrown with this Message. If you expected this to happen, please call a
 			// corresponding assertion function from ServiceTests to silence this message.
-
+			UNSCOPED_INFO("Found " << ServiceTests::countErrors() << " errors at end of section: ");
+			for (auto error: ServiceTests::getThrownErrors()) {
+				UNSCOPED_INFO("  Error " << error.second << " (type " << error.first << ")");
+			}
 			CHECK(ServiceTests::hasNoErrors());
 		}
+
+		ServiceTests::resetErrors();
 	}
 
 	void testCaseEnded(Catch::TestCaseStats const& testCaseStats) override {
diff --git a/test/Time/TimeStampTests.cpp b/test/Time/TimeStampTests.cpp
index f01a462ec53c55b38a1df9eb152690bda26671cd..daf3df672d0ef6e12ce647836b8156feeb6bf6a3 100644
--- a/test/Time/TimeStampTests.cpp
+++ b/test/Time/TimeStampTests.cpp
@@ -1,7 +1,9 @@
+#include "../Services/ServiceTests.hpp"
 #include "Time/TimeStamp.hpp"
 #include "catch2/catch_all.hpp"
 
 using namespace Time;
+using Catch::Approx;
 
 TEST_CASE("TimeStamp class construction") {
 	// SECTION("Initialize with excessive precision, breaks at compile time"){
@@ -40,7 +42,7 @@ TEST_CASE("TAI idempotence") {
 TEST_CASE("CUC idempotence") {
 	etl::array<uint8_t, 9> input1 = {0b00101010, 0, 1, 1, 3, 0, 0, 0, 0};
 	TimeStamp<3, 2> time1(input1);
-	etl::array<uint8_t, 9> cuc1 = time1.toCUCtimestamp();
+	etl::array<uint8_t, 9> cuc1 = time1.formatAsCUC();
 
 	for (uint8_t i = 0; i < 9; i++) {
 		CHECK(input1[i] == cuc1[i]);
@@ -48,7 +50,7 @@ TEST_CASE("CUC idempotence") {
 
 	etl::array<uint8_t, 9> input2 = {0b10101101, 0b10100000, 218, 103, 11, 0, 3, 23, 0};
 	TimeStamp<5, 1> time2(input2);
-	etl::array<uint8_t, 9> cuc2 = time2.toCUCtimestamp();
+	etl::array<uint8_t, 9> cuc2 = time2.formatAsCUC();
 
 	for (auto i = 0; i < 9; i++) {
 		CHECK(input2[i] == cuc2[i]);
@@ -56,7 +58,7 @@ TEST_CASE("CUC idempotence") {
 
 	etl::array<uint8_t, 9> input3 = {0b10100011, 0b10001100, 218, 103, 11, 0, 3, 23, 2};
 	TimeStamp<1, 6> time3(input3);
-	etl::array<uint8_t, 9> cuc3 = time3.toCUCtimestamp();
+	etl::array<uint8_t, 9> cuc3 = time3.formatAsCUC();
 
 	for (auto i = 0; i < 9; i++) {
 		CHECK(input3[i] == cuc3[i]);
@@ -66,7 +68,7 @@ TEST_CASE("CUC idempotence") {
 TEST_CASE("Conversion between CUC formats") {
 	SECTION("Base unit conversion") {
 		TimeStamp<2, 2> time1(20123);
-		TimeStamp<5, 2> time2(time1.toCUCtimestamp());
+		TimeStamp<5, 2> time2(time1.formatAsCUC());
 		CHECK(time1.asTAIseconds() == time2.asTAIseconds());
 	}
 
@@ -74,7 +76,7 @@ TEST_CASE("Conversion between CUC formats") {
 		etl::array<uint8_t, 9> timeInput = {0b00101010, 0, 1, 1, 3, 0, 0, 0, 0};
 		TimeStamp<3, 2> time1(timeInput);
 
-		TimeStamp<3, 5> time2(time1.toCUCtimestamp());
+		TimeStamp<3, 5> time2(time1.formatAsCUC());
 		CHECK(time1.asTAIseconds() == time2.asTAIseconds());
 	}
 
@@ -82,7 +84,7 @@ TEST_CASE("Conversion between CUC formats") {
 		etl::array<uint8_t, 9> timeInput = {0b00101010, 0, 1, 1, 3, 0, 0, 0, 0};
 		TimeStamp<3, 2> time1(timeInput);
 
-		TimeStamp<4, 4> time2(time1.toCUCtimestamp());
+		TimeStamp<4, 4> time2(time1.formatAsCUC());
 		CHECK(time1.asTAIseconds() == time2.asTAIseconds());
 	}
 }
@@ -95,7 +97,7 @@ TEST_CASE("Use of custom Acubesat CUC format") {
 		CHECK(time1.asCustomCUCTimestamp().elapsed100msTicks == 1000);
 		TimeStamp<3, 2> time2(customCUC1);
 		CHECK(time2.asTAIseconds() == 100);
-		CHECK(time2.asCustomCUCTimestamp().elapsed100msTicks == 1000);
+		CHECK(time2.asCustomCUCTimestamp().elapsed100msTicks == 1001);
 
 		// check rounding errors
 		Time::CustomCUC_t customCUC2 = {1004};
@@ -104,13 +106,13 @@ TEST_CASE("Use of custom Acubesat CUC format") {
 		CHECK(time3.asCustomCUCTimestamp().elapsed100msTicks == 1000);
 		TimeStamp<3, 2> time4(customCUC2);
 		CHECK(time4.asTAIseconds() == 100);
-		CHECK(time4.asCustomCUCTimestamp().elapsed100msTicks == 1003);
+		CHECK(time4.asCustomCUCTimestamp().elapsed100msTicks == 1004);
 
 		// check rounding errors
 		Time::CustomCUC_t customCUC3 = {1005};
 		TimeStamp<3, 0> time5(customCUC3);
-		CHECK(time5.asTAIseconds() == 100);
-		CHECK(time5.asCustomCUCTimestamp().elapsed100msTicks == 1000);
+		CHECK(time5.asTAIseconds() == 101);
+		CHECK(time5.asCustomCUCTimestamp().elapsed100msTicks == 1010);
 		TimeStamp<3, 2> time6(customCUC3);
 		CHECK(time6.asTAIseconds() == 100);
 		CHECK(time6.asCustomCUCTimestamp().elapsed100msTicks == 1005);
@@ -143,22 +145,43 @@ TEST_CASE("UTC idempotence") {
 
 TEST_CASE("UTC conversion to and from seconds timestamps") {
 	{
-		UTCTimestamp timestamp1(2020, 12, 5, 0, 0, 0);
-		TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(timestamp1);
+		UTCTimestamp utc(2020, 12, 5, 0, 0, 0);
+		TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(utc);
 		REQUIRE(time.asTAIseconds() == 29289600);
 	}
 	{
-		UTCTimestamp timestamp1(2020, 2, 29, 0, 0, 0);
-		TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(timestamp1);
+		UTCTimestamp utc(2020, 2, 29, 0, 0, 0);
+		TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(utc);
 		REQUIRE(time.asTAIseconds() == 5097600);
 	}
 	{
-		UTCTimestamp timestamp1(2025, 3, 10, 0, 0, 0);
-		TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(timestamp1);
+		UTCTimestamp utc(2025, 3, 10, 0, 0, 0);
+		TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(utc);
 		REQUIRE(time.asTAIseconds() == 163728000);
 	}
 }
 
+TEST_CASE("UTC overflow tests") {
+	SECTION("Year too high") {
+		UTCTimestamp utc(2999, 3, 11, 0, 0, 0);
+		TimeStamp<2, 1> time(utc);
+		REQUIRE(ServiceTests::thrownError(ErrorHandler::TimeStampOutOfBounds));
+		ServiceTests::reset();
+	}
+	SECTION("Seconds too high, small variable") {
+		UTCTimestamp utc(Epoch.year, Epoch.month, Epoch.day, 0, 7, 0);
+		TimeStamp<1, 1> time(utc);
+		REQUIRE(ServiceTests::thrownError(ErrorHandler::TimeStampOutOfBounds));
+		ServiceTests::reset();
+	}
+	SECTION("Seconds too high, wide variable") {
+		UTCTimestamp utc(Epoch.year, Epoch.month, Epoch.day, 0, 7, 0);
+		TimeStamp<1, 4> time(utc);
+		REQUIRE(ServiceTests::thrownError(ErrorHandler::TimeStampOutOfBounds));
+		ServiceTests::reset();
+	}
+}
+
 // SECTION("Check different templates, should break at compile"){
 //   TimeStamp<1, 2> time1;
 //   TimeStamp<4, 4> time2;
@@ -166,25 +189,143 @@ TEST_CASE("UTC conversion to and from seconds timestamps") {
 // }
 
 TEST_CASE("Time operators") {
-	TimeStamp<1, 2> time1;
-	TimeStamp<1, 2> time2;
-	TimeStamp<1, 2> time3(10);
-	TimeStamp<1, 2> time4(12);
-	TimeStamp<1, 2> time5(10);
-	TimeStamp<2, 2> time6;
-	REQUIRE(time1 == time2);
-	REQUIRE(time2 == time1);
-	REQUIRE(time3 == time5);
-	REQUIRE(time1 != time3);
-	REQUIRE(time3 != time4);
-	REQUIRE(time3 <= time4);
-	REQUIRE(time3 < time4);
-
-	// REQUIRE(time1 == time6); //should fail at compile, different templates
+	SECTION("Same type") {
+		TimeStamp<1, 2> time1;
+		TimeStamp<1, 2> time2;
+		TimeStamp<1, 2> time3(10);
+		TimeStamp<1, 2> time4(12);
+		TimeStamp<1, 2> time5(10);
+		CHECK(time1 == time2);
+		CHECK(time2 == time1);
+		CHECK(time3 == time5);
+		CHECK(time1 != time3);
+		CHECK(time3 != time4);
+		CHECK(time3 <= time4);
+		CHECK(time3 < time4);
+	}
+
+	SECTION("Different size") {
+		TimeStamp<1, 2> time1(10);
+		TimeStamp<2, 1> time2(10);
+		TimeStamp<3, 2> time3(15);
+		TimeStamp<2, 2> time4(5);
+		CHECK(time1 == time2);
+		CHECK(time3 != time2);
+		CHECK(time4 < time2);
+		CHECK(time3 > time4);
+		CHECK(time2 >= time1);
+		CHECK(time1 <= time3);
+	}
+
+	SECTION("Different units") {
+		TimeStamp<1, 2, 10> time1(10);
+		TimeStamp<1, 2, 1, 10> time2(10);
+		TimeStamp<3, 2, 3, 2> time3(15);
+		TimeStamp<2, 2, 57, 89> time4(5);
+		CHECK(time1 == time2);
+		CHECK(time3 != time2);
+		CHECK(time4 < time2);
+		CHECK(time3 > time4);
+		CHECK(time2 >= time1);
+		CHECK(time1 <= time3);
+	}
+
+	SECTION("Overflow") {
+		TimeStamp<1, 0> time1(1);
+		TimeStamp<4, 4> time2(std::numeric_limits<uint32_t>::max());
+
+		CHECK(time1 != time2);
+		CHECK_FALSE(time1 == time2);
+		CHECK(time1 < time2);
+		CHECK(time1 <= time2);
+		CHECK(time2 > time1);
+		CHECK(time2 >= time1);
+	}
 }
 
 TEST_CASE("Time runtime class size") {
-	int input_time = 1000;
-	TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(input_time);
-	REQUIRE(sizeof(time) < 32);
+	REQUIRE(sizeof(TimeStamp<CUCSecondsBytes, CUCFractionalBytes>) <= 8);
+}
+
+TEST_CASE("CUC conversions") {
+	SECTION("Base unit, without fractions") {
+		TimeStamp<2, 0, 10, 1> time1(100);
+		CHECK(time1.asTAIseconds() == 100);
+
+		TimeStamp<2, 0, 1, 10> time2(time1);
+		CHECK(time2.asTAIseconds() == 100);
+	}
+
+	SECTION("Base unit, with fractions") {
+		TimeStamp<2, 2, 10, 1> time1(100);
+		CHECK(time1.asTAIseconds() == 100);
+
+		TimeStamp<2, 2, 1, 10> time2(time1);
+		CHECK(time2.asTAIseconds() == 100);
+	}
+
+	SECTION("Addition of fraction") {
+		TimeStamp<2, 0, 1, 1> time1(100);
+		CHECK(time1.asTAIseconds() == 100);
+
+		TimeStamp<2, 2, 1, 1> time2(time1);
+		CHECK(time2.asTAIseconds() == 100);
+	}
+
+	SECTION("Removal of fraction") {
+		TimeStamp<2, 2, 1, 1> time1(100);
+		CHECK(time1.asTAIseconds() == 100);
+
+		TimeStamp<2, 0, 1, 1> time2(time1);
+		CHECK(time2.asTAIseconds() == 100);
+	}
+
+	SECTION("Many changes") {
+		TimeStamp<2, 2, 3, 2> time1(1000);
+		CHECK(time1.asTAIseconds() == Approx(1000).epsilon(1));
+
+		TimeStamp<3, 4, 100, 29> time2(time1);
+		CHECK(time2.asTAIseconds() == Approx(1000).epsilon(1));
+
+		TimeStamp<2, 1, 1, 1> time3(time1);
+		CHECK(time3.asTAIseconds() == Approx(1000).epsilon(1));
+	}
+
+	SECTION("Large numbers") {
+		TimeStamp<4, 0, 1, 1> time1(10000);
+		CHECK(time1.asTAIseconds() == 10000);
+
+		TimeStamp<2, 1, 7907, 7559> time2(time1);
+		CHECK(time2.asTAIseconds() == 9999);
+	}
+}
+
+TEST_CASE("Duration conversions") {
+	using namespace std::chrono_literals;
+
+	SECTION("Conversion to duration") {
+		TimeStamp<2, 2, 1, 1> time(3600);
+		auto duration = time.asDuration<std::chrono::hours>();
+		CHECK(duration == 1h);
+	}
+
+	SECTION("Conversion from duration") {
+		auto duration = 90min;
+		TimeStamp<2, 2, 1, 1> time(duration);
+		CHECK(time.asTAIseconds() == 5400);
+	}
+
+	SECTION("Duration idempotence") {
+		auto duration = 13532s;
+		TimeStamp<2, 2, 1, 1> time(duration);
+
+		CHECK(time.asDuration() == duration);
+	}
+
+	SECTION("Overflow") {
+		auto duration = 24h;
+		TimeStamp<2, 2, 1, 1> time(duration);
+
+		CHECK(time.asTAIseconds() == 20864);
+	}
 }