diff --git a/CMakeLists.txt b/CMakeLists.txt
index 227d2632f485b8692952f3bb3f140c080bad21a1..37310bc340db176e5788eafbd34689ae763630d1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -29,7 +29,7 @@ add_library(common OBJECT
         src/MessageParser.cpp
         src/ServicePool.cpp
         src/Helpers/CRCHelper.cpp
-        src/Helpers/TimeFormats.cpp
+        src/Time/UTCTimestamp.cpp
         src/Services/EventReportService.cpp
         src/Services/MemoryManagementService.cpp
         src/Services/ParameterService.cpp
@@ -47,8 +47,7 @@ add_library(common OBJECT
         )
 
 # Specify the .cpp files for the executables
-file(GLOB x86_main_SRC "src/Platform/x86/*.cpp" "src/Platform/x86/Services/*.cpp"
-        "lib/logger/src/Platform/x86/*.cpp")
+file(GLOB_RECURSE x86_main_SRC "src/Platform/x86/*.cpp" "lib/logger/src/Platform/x86/*.cpp")
 
 add_executable(ecss_services
         $<TARGET_OBJECTS:common>
diff --git a/inc/Helpers/TimeFormats.hpp b/inc/Helpers/TimeFormats.hpp
deleted file mode 100644
index 969c52da5ce3787801fb6a7cbf57f2591c98df25..0000000000000000000000000000000000000000
--- a/inc/Helpers/TimeFormats.hpp
+++ /dev/null
@@ -1,221 +0,0 @@
-#include <cstdint>
-#include <Message.hpp>
-#include "macros.hpp"
-#include "etl/String.hpp"
-
-// SEE CCSDS 301.0-B-4
-
-//////////////// CONSTANTS ////////////////////
-inline constexpr uint8_t SECONDS_PER_MINUTE = 60;
-inline constexpr uint16_t SECONDS_PER_HOUR = 3600;
-inline constexpr uint32_t SECONDS_PER_DAY = 86400;
-static constexpr uint8_t DAYSOFMONTH[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
-
-inline constexpr uint8_t ACUBESAT_CUC_SECONDS_COUNTER_BYTES = 2; // PER DDJF_TTC
-inline constexpr uint8_t ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES = 2; // PER DDJF_TTC
-// Unix Epoch includes leap seconds.
-// Currently set at 01 Jan 2019 00h00m00s, TODO correct to set to 01 Jan. 2020 00h00m00s.
-// May be broken, see Issue #121 on Gitlab.
-inline constexpr uint32_t UNIX_TO_ACUBESAT_EPOCH_ELAPSED_SECONDS = 1546300800;
-//
-inline constexpr uint16_t ACUBESAT_EPOCH_YEAR = 2019;
-inline constexpr uint8_t ACUBESAT_EPOCH_MONTH = 1;
-inline constexpr uint8_t ACUBESAT_EPOCH_DAY = 1;
-
-inline constexpr uint8_t MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP = 9;
-
-static_assert(ACUBESAT_EPOCH_YEAR >= 2019);
-static_assert(ACUBESAT_EPOCH_MONTH < 11 && ACUBESAT_EPOCH_MONTH >= 0);
-static_assert(ACUBESAT_EPOCH_DAY < DAYSOFMONTH[ACUBESAT_EPOCH_MONTH]);
-//////////////////////////////////////////////
-
-//////// HELPER CONSTEXPR ////////
-// Build CUC header at compile time for timestamps that have a short (1 byte) header
-template <int seconds_counter_bytes, int fractional_counter_bytes>
-inline constexpr uint8_t build_short_CUC_header() {
-	static_assert(seconds_counter_bytes <= 4, "Use build_long_CUC_header instead");
-	static_assert(fractional_counter_bytes <= 3, "Use build_long_CUC_header instead");
-
-	uint8_t header = 0;
-
-	// P-Field extension is 0, CUC header is not extended
-	header += 0;
-
-	// AcubeSAT is using custom TAI epoch at 01 Jan 2020
-	header = header << 3;
-	header += 0b010;
-
-	// Number of bytes in the basic time unit
-	header = header << 2;
-	header += seconds_counter_bytes - 1;
-
-	// Number of bytes in the fractional unit
-	header = header << 2;
-	header += fractional_counter_bytes;
-
-	return header;
-}
-
-// Build CUC header at compile time for timestamps that have a long (2 byte) header
-template <int seconds_counter_bytes, int fractional_counter_bytes>
-inline constexpr uint16_t build_long_CUC_header() {
-	// cppcheck-suppress redundantCondition
-	static_assert(seconds_counter_bytes > 4 || fractional_counter_bytes > 3, "Use build_short_CUC_header instead");
-	static_assert(seconds_counter_bytes <= 7,
-	              "Number of bytes for seconds over maximum number of octets allowed by CCSDS");
-	static_assert(fractional_counter_bytes <= 6,
-	              "Number of bytes for seconds over maximum number of octets allowed by CCSDS");
-
-	uint16_t header = 0;
-
-	uint8_t first_octet_number_of_seconds_bytes = std::min(4, seconds_counter_bytes);
-	uint8_t second_octet_number_of_seconds_bytes = seconds_counter_bytes - first_octet_number_of_seconds_bytes;
-
-	uint8_t first_octet_number_of_fractional_bytes = std::min(3, fractional_counter_bytes);
-	uint8_t second_octet_number_of_fractional_bytes = fractional_counter_bytes - first_octet_number_of_fractional_bytes;
-
-	// P-Field extension is 1, CUC header is extended
-	header += 1;
-
-	// AcubeSAT is using custom TAI epoch at 01 Jan 2020
-	header = header << 3;
-	header += 0b010;
-
-	// // Number of bytes in the basic time unit
-	header = header << 2;
-	header += first_octet_number_of_seconds_bytes - 1;
-
-	// Number of bytes in the fractional unit
-	header = header << 2;
-	header += first_octet_number_of_fractional_bytes;
-
-	// P-Field extension is 1, CUC header was extended
-	header = header << 1;
-	header += 1;
-
-	// Number of bytes in the extended basic time unit
-	header = header << 2;
-	header += second_octet_number_of_seconds_bytes;
-
-	// Number of bytes in the extended fractional unit
-	header = header << 3;
-	header += second_octet_number_of_fractional_bytes;
-
-	// Last 3 LSb are reserved for custom mission use
-	header = header << 2;
-	header += 0;
-
-	return header;
-}
-
-template <typename T, int seconds_counter_bytes, int fractional_counter_bytes>
-inline constexpr T build_CUC_header() {
-	static_assert((seconds_counter_bytes + fractional_counter_bytes) <= 8,
-	              "Complete arbitrary precision not yet supported"); // TODO: see Issue #106 on Gitlab
-	// cppcheck-suppress syntaxError
-	// cppcheck-suppress redundantCondition
-	if constexpr (seconds_counter_bytes <= 4 &&
-	              fractional_counter_bytes <= 3) // if constexpr not supported yet in cppcheck
-		return build_short_CUC_header<seconds_counter_bytes, fractional_counter_bytes>();
-	else
-		return build_long_CUC_header<seconds_counter_bytes, fractional_counter_bytes>();
-}
-
-inline constexpr uint8_t build_AcubeSAT_CDS_header() {
-	uint8_t header = 0;
-
-	// bit 0 is at 0
-	header += 0;
-	header << 1;
-
-	// timecode identification
-	header += 0b100;
-	header << 3;
-
-	// AcubeSAT is using custom TAI epoch at 01 Jan 2020
-	header += 1;
-	header << 1;
-
-	// AcubeSAT is using 16 bits day count segment
-	header += 0;
-	header << 1;
-
-	// AcubeSAT is using picosecond resolution
-	header += 0b10;
-	// header << 2;
-
-	return header;
-}
-////////////////////////////////////////////////
-
-////////// CONVENIENCE FUNCTIONS ////////////
-bool is_leap_year(uint16_t year);
-
-////////// Transitory timestamps ////////////
-// CUSTOM EPOCH FOR ALL ACUBESAT TIMESTAMPS IS 01 JAN 2020, EXCEPT UTC (UNIX)
-class AcubeSAT_CDS_timestamp {
-public:
-	static constexpr uint8_t P_FIELD = build_AcubeSAT_CDS_header();
-	uint16_t day;
-	uint16_t ms_of_day;
-	uint32_t submilliseconds;
-
-	uint64_t to_CDS_timestamp();
-	void from_CDS_timestamp(uint64_t);
-};
-
-// CUSTOM EPOCH FOR ALL ACUBESAT TIMESTAMPS IS 01 JAN 2020, EXCEPT UTC (UNIX)
-/**
- * A class that represents UTC time and date, from UNIX EPOCH
- *
- * @note
- * This class represents UTC (Coordinated Universal Time) date
- */
-class UTC_Timestamp {
-public:
-	uint16_t year;
-	uint8_t month;
-	uint8_t day;
-	uint8_t hour;
-	uint8_t minute;
-	uint8_t second;
-
-	/**
-	 * Assign the instances with the Unix epoch 1/1/1970 00:00:00
-	 */
-	UTC_Timestamp();
-
-	/**
-	 * @param year the year as it used in Gregorian calendar
-	 * @param month the month as it used in Gregorian calendar
-	 * @param day the day as it used in Gregorian calendar
-	 * @param hour UTC hour in 24 format
-	 * @param minute UTC minutes
-	 * @param second UTC seconds
-	 */
-	UTC_Timestamp(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second);
-
-	/**
-	 * @param text_timestamp the timestamp to parse into a UTC date
-	 */
-	UTC_Timestamp(etl::string<32> text_timestamp);
-
-	/**
-	 * Compare two timestamps.
-	 *
-	 * @param Date the date that will be compared with the pointer `this`
-	 * @return true if the condition is satisfied
-	 */
-	bool operator<(const UTC_Timestamp& Date);
-	bool operator>(const UTC_Timestamp& Date);
-	bool operator==(const UTC_Timestamp& Date);
-	bool operator<=(const UTC_Timestamp& Date);
-	bool operator>=(const UTC_Timestamp& Date);
-
-	/**
-	 * Pretty-print timestamp.
-	 *
-	 * @param Date the date that will be output
-	 */
-	friend std::ostream& operator<<(std::ostream& o, UTC_Timestamp const& Date);
-};
diff --git a/inc/Helpers/TimeStamp.hpp b/inc/Helpers/TimeStamp.hpp
deleted file mode 100644
index ef8190952f6ceefbac0a3a848093836c34a1b6bd..0000000000000000000000000000000000000000
--- a/inc/Helpers/TimeStamp.hpp
+++ /dev/null
@@ -1,120 +0,0 @@
-#ifndef ECSS_SERVICES_TIME_HPP
-#define ECSS_SERVICES_TIME_HPP
-
-#include <cstdint>
-#include <algorithm>
-#include "macros.hpp"
-#include <etl/array.h>
-#include "Helpers/TimeFormats.hpp"
-
-// SEE CCSDS 301.0-B-4
-// CUSTOM EPOCH FOR ALL ACUBESAT TIMESTAMPS IS 01 JAN 2020, EXCEPT UTC (UNIX)
-
-///////////// CLASS DECLARATION ////////////////
-/**
- * A class that represents an instant in time, with convenient conversion
- * to and from usual time and date representations
- *
- * @note
- * This class uses internally TAI time, and handles UTC leap seconds at conversion to and
- * from UTC time system.
- */
-template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes>
-class TimeStamp {
-private:
-	static_assert(seconds_counter_bytes + fractional_counter_bytes <= 8,
-	              "Currently, this class is not suitable for storage on internal counter larger than uint64_t");
-	typedef typename std::conditional
-			<
-			seconds_counter_bytes < 4 && fractional_counter_bytes < 3,
-			uint8_t,
-			uint16_t
-			>
-			::type CUC_header_t;
-	typedef typename std::conditional
-			<
-			(seconds_counter_bytes + fractional_counter_bytes) < 4,
-			uint32_t,
-			uint64_t
-			>
-			::type tai_counter_t;
-	tai_counter_t tai_counter;
-	CUC_header_t CUC_header = build_CUC_header<CUC_header_t, seconds_counter_bytes, fractional_counter_bytes>();
-
-public:
-	/**
-	 * Initialize the instant at epoch
-	 *
-	 */
-	TimeStamp() : tai_counter(0){};
-
-	/**
-	 * Initialize the instant from a duration from epoch in TAI
-	 *
-	 * @param seconds an integer number of seconds from AcubeSAT custom epoch
-	 */
-	TimeStamp(int tai_seconds_from_AcubeSAT_epoch);
-	
-	/**
-	 * Initialize the instant from the bytes of a CUC time stamp
-	 *
-	 * @param timestamp a complete CUC time stamp including header, of the maximum possible size, zero padded to the
-	 * right
-	 */
-	TimeStamp(etl::array<uint8_t, MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP> timestamp);
-
-	/**
-	 * Initialize the instant from a UTC timestamp struct
-	 *
-	 * @param timestamp a UTC timestamp, from Unix Epoch
-	 */
-	TimeStamp(UTC_Timestamp timestamp);
-
-	/**
-	 * Initialize the instant from an AcubeSAT CDS timestamp
-	 *
-	 * @param timestamp a CDS timestamp, in the format from DDJF_TTC, from epoch 2020.01.01
-	 */
-	TimeStamp(AcubeSAT_CDS_timestamp timestamp);
-
-	/**
-	 * Get the representation as seconds from epoch in TAI
-	 *
-	 * @return the seconds elapsed in TAI since 1 Jan 1958, cut to the integer part
-	 */
-	const int as_TAI_seconds();
-
-	/**
-	 * Get the representation as CUC formatted bytes
-	 *
-	 * @return the instant, represented in the CCSDS CUC format
-	 */
-	const etl::array<uint8_t, MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP> as_CUC_timestamp();
-
-	/**
-	 * Get the representation as a UTC timestamp
-	 *
-	 * @return the instant, represented in the structure that holds UTC timestamps
-	 */
-	const UTC_Timestamp as_UTC_timestamp();
-
-	/**
-	 * Compare two instants.
-	 *
-	 * @param TimeStamp the date that will be compared with the pointer `this`
-	 * @return true if the condition is satisfied
-	 */
-	bool operator<(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const;
-	bool operator>(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const;
-	bool operator==(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const;
-	bool operator!=(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const;
-	bool operator<=(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const;
-	bool operator>=(const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const;
-};
-////////////////////////////////////////////////
-
-////// TEMPLATED METHODS INSTANTIATION /////////
-#include "TimeStamp.tpp"
-////////////////////////////////////////////////
-
-#endif
diff --git a/inc/Helpers/TimeStamp.tpp b/inc/Helpers/TimeStamp.tpp
deleted file mode 100644
index 1d70e753cd94a215fa6d9f368e05c70d23f65423..0000000000000000000000000000000000000000
--- a/inc/Helpers/TimeStamp.tpp
+++ /dev/null
@@ -1,240 +0,0 @@
-#define SECONDS_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1 0b00001100
-#define SECONDS_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE2 0b01100000
-#define FRACTIONAL_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1 0b00000011
-#define FRACTIONAL_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE2 0b00011100
-
-#define SECONDS_FROM_SINGLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1 0b00001100
-#define FRACTIONAL_FROM_SINGLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1 0b00000011
-
-#include <math.h>
-
-bool is_leap_year(uint16_t year);
-
-////////////: CONSTRUCTORS ////////////
-//// FROM TAI SECONDS
-template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes>
-TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::TimeStamp(int tai_seconds_from_AcubeSAT_epoch) {
-  static constexpr uint64_t max_seconds_counter_value = static_cast<uint64_t>(seconds_counter_bytes << 2) - 1;
-  // cppcheck-suppress useInitializationList
-  tai_counter = static_cast<tai_counter_t>(tai_seconds_from_AcubeSAT_epoch) << 8 * fractional_counter_bytes;
-  ASSERT_INTERNAL(tai_seconds_from_AcubeSAT_epoch >= max_seconds_counter_value, ErrorHandler::InternalErrorType::InvalidTimeStampInput);
-}
-
-//// FROM CDS TIMESTAMP
-template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes>
-TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::TimeStamp(AcubeSAT_CDS_timestamp timestamp) {
-	// tai_counter = 0; //TODO: see Issue #105 on Gitlab
-}
-
-//// FROM CUC TIMESTAMP
-template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes>
-TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::TimeStamp(
-    etl::array<uint8_t, MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP> timestamp) {
-	// process header
-	int header_size = 1;
-	if (timestamp[0] & 0b10000000) {
-		header_size = 2;
-	};
-
-	int timestamp_fractional_bytes_count = 0;
-	int timestamp_seconds_bytes_count = 1;
-
-	if (header_size == 2) {
-		timestamp_seconds_bytes_count += (timestamp[0] & SECONDS_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1) >> 2;
-		timestamp_seconds_bytes_count += (timestamp[1] & SECONDS_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE2) >> 5;
-		timestamp_fractional_bytes_count =
-		    ((timestamp[0] & FRACTIONAL_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1) >> 0) +
-		    ((timestamp[1] & FRACTIONAL_FROM_DOUBLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE2) >> 2);
-	} else if (header_size == 1) {
-		timestamp_seconds_bytes_count += (timestamp[0] & SECONDS_FROM_SINGLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1) >> 2;
-		timestamp_fractional_bytes_count =
-		    (timestamp[0] & FRACTIONAL_FROM_SINGLE_BYTE_CUC_TIMESTAMP_BITMASK_BYTE1) >> 0;
-	} else {
-		ASSERT_INTERNAL(true, ErrorHandler::InternalErrorType::InvalidTimeStampInput);
-	}
-
-	// check input validity (useless bytes set to 0)
-	int err = 0;
-	for (int i = header_size + timestamp_seconds_bytes_count + timestamp_fractional_bytes_count; i < 9; i++) {
-		if (timestamp[i] != 0) {
-			err += 1;
-			break;
-		}
-	}
-	ASSERT_INTERNAL(err == 0, ErrorHandler::InternalErrorType::InvalidTimeStampInput);
-
-	// do checks wrt template precision parameters
-	ASSERT_INTERNAL(timestamp_seconds_bytes_count <= seconds_counter_bytes,
-	                ErrorHandler::InternalErrorType::InvalidTimeStampInput);
-	ASSERT_INTERNAL(timestamp_fractional_bytes_count <= fractional_counter_bytes,
-	                ErrorHandler::InternalErrorType::InvalidTimeStampInput);
-
-	// put timestamp into internal counter
-	tai_counter = 0;
-	// add seconds until run out of bytes on input array
-	for (auto i = 0; i < timestamp_seconds_bytes_count; i++) {
-		tai_counter = tai_counter << 8;
-		tai_counter += timestamp[header_size + i];
-	}
-	// add fractional until run out of bytes on input array
-	for (auto i = 0; i < timestamp_fractional_bytes_count; i++) {
-		tai_counter = tai_counter << 8;
-		tai_counter += timestamp[header_size + timestamp_seconds_bytes_count + i];
-	}
-	// pad rightmost bytes to full length
-	tai_counter = tai_counter << 8 * (fractional_counter_bytes - timestamp_fractional_bytes_count);
-}
-
-//// FROM UTC TIMESTAMP
-template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes>
-TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::TimeStamp(UTC_Timestamp timestamp) {
-	int seconds = UNIX_TO_ACUBESAT_EPOCH_ELAPSED_SECONDS;
-	for (int year = ACUBESAT_EPOCH_YEAR; year < timestamp.year; ++year) {
-		seconds += (is_leap_year(year) ? 366 : 365) * SECONDS_PER_DAY;
-	}
-	for (int month = ACUBESAT_EPOCH_MONTH; month < timestamp.month; ++month) {
-		seconds += DAYSOFMONTH[month - 1] * SECONDS_PER_DAY;
-		if ((month == 2U) && is_leap_year(timestamp.year)) {
-			seconds += SECONDS_PER_DAY;
-		}
-	}
-	seconds += (timestamp.day - ACUBESAT_EPOCH_DAY) * SECONDS_PER_DAY;
-	seconds += timestamp.hour * SECONDS_PER_HOUR;
-	seconds += timestamp.minute * SECONDS_PER_MINUTE;
-	seconds += timestamp.second;
-	tai_counter = static_cast<tai_counter_t>(seconds) << 8 * fractional_counter_bytes;
-}
-
-////////////// GETTER ///////////////
-template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes>
-const int TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::as_TAI_seconds() {
-	return tai_counter >> (8 * fractional_counter_bytes);
-}
-
-template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes>
-const etl::array<uint8_t, MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP>
-TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::as_CUC_timestamp() {
-	etl::array<uint8_t, MAXIMUM_BYTES_FOR_COMPLETE_CUC_TIMESTAMP> return_array = {0};
-	int index_first_non_header_byte;
-
-  // cppcheck-suppress redundantCondition
-	static constexpr uint8_t header_size = seconds_counter_bytes < 4 && fractional_counter_bytes < 3 ? 1 : 2; // number of bytes in CUC header
-
-	if (header_size == 1) {
-		return_array[0] = static_cast<uint8_t>(CUC_header);
-		index_first_non_header_byte = 1;
-	}
-
-	else { // two-bytes CUC header
-		return_array[1] = static_cast<uint8_t>(CUC_header);
-		return_array[0] = static_cast<uint8_t>(CUC_header >> 8);
-		index_first_non_header_byte = 2;
-	}
-
-	for (auto byte_being_filled_in_return = 0;
-	     byte_being_filled_in_return < seconds_counter_bytes + fractional_counter_bytes;
-	     byte_being_filled_in_return++) {
-		int index_of_interesting_byte_in_tai_counter =
-		    8 * (seconds_counter_bytes + fractional_counter_bytes - byte_being_filled_in_return - 1);
-		return_array[index_first_non_header_byte + byte_being_filled_in_return] =
-		    tai_counter >> index_of_interesting_byte_in_tai_counter;
-	}
-
-	return return_array;
-}
-
-template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes>
-const UTC_Timestamp TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::as_UTC_timestamp() {
-	int seconds = as_TAI_seconds();
-
-	// elapsed seconds should be between dates, that are after 1/1/2019 and Unix epoch
-	ASSERT_INTERNAL(seconds >= UNIX_TO_ACUBESAT_EPOCH_ELAPSED_SECONDS, ErrorHandler::InternalErrorType::InvalidDate);
-
-	seconds -= UNIX_TO_ACUBESAT_EPOCH_ELAPSED_SECONDS; // elapsed seconds from Unix epoch until AcubeSAT custom epoch 00:00:00 (UTC)
-	int year_utc = ACUBESAT_EPOCH_YEAR;
-	int month_utc = ACUBESAT_EPOCH_MONTH;
-	int day_utc = ACUBESAT_EPOCH_DAY;
-	int hour = 0;
-	int minute = 0;
-	int second = 0;
-
-	// calculate years
-	while (seconds >= (is_leap_year(year_utc) ? 366 : 365) * SECONDS_PER_DAY) {
-		seconds -= (is_leap_year(year_utc) ? 366 : 365) * SECONDS_PER_DAY;
-		year_utc++;
-	}
-
-	// calculate months
-	int current_month = 0;
-	while (seconds >= (DAYSOFMONTH[current_month] * SECONDS_PER_DAY)) {
-		month_utc++;
-		seconds -= (DAYSOFMONTH[current_month] * SECONDS_PER_DAY);
-		current_month++;
-		if ((current_month == 1U) && is_leap_year(year_utc)) {
-			if (seconds <= (28 * SECONDS_PER_DAY)) {
-				break;
-			}
-			month_utc++;
-			seconds -= 29 * SECONDS_PER_DAY;
-			current_month++;
-		}
-	}
-
-	// calculate days
-	day_utc = seconds / SECONDS_PER_DAY;
-	seconds -= day_utc * SECONDS_PER_DAY;
-	day_utc++; // add 1 day because we start count from 1 January (and not 0 January!)
-
-	// calculate hours
-	hour = seconds / SECONDS_PER_HOUR;
-	seconds -= hour * SECONDS_PER_HOUR;
-
-	// calculate minutes
-	minute = seconds / SECONDS_PER_MINUTE;
-	seconds -= minute * SECONDS_PER_MINUTE;
-
-	// calculate seconds
-	second = seconds;
-
-	return UTC_Timestamp(year_utc, month_utc, day_utc, hour, minute, second);
-}
-
-////////////// OPERATORS ///////////
-/// COMPARISON
-template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes>
-bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator==(
-    const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const {
-	return tai_counter == TimeStamp.tai_counter;
-}
-
-template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes>
-bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator!=(
-    const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const {
-	return tai_counter != TimeStamp.tai_counter;
-}
-
-template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes>
-bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator<(
-    const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const {
-	return tai_counter < TimeStamp.tai_counter;
-}
-
-template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes>
-bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator>(
-    const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const {
-	return tai_counter > TimeStamp.tai_counter;
-}
-
-template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes>
-bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator<=(
-    const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const {
-	return tai_counter <= TimeStamp.tai_counter;
-}
-
-template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes>
-bool TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::operator>=(
-    const TimeStamp<seconds_counter_bytes, fractional_counter_bytes>& TimeStamp) const {
-	return tai_counter >= TimeStamp.tai_counter;
-}
-/// ARITHMETIC
-// TODO: see Issue #104 on Gitlab repository
diff --git a/inc/Platform/x86/Helpers/UTCTimestamp.hpp b/inc/Platform/x86/Helpers/UTCTimestamp.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d6b5eed4059e48f8c713158be277191bdb4c923e
--- /dev/null
+++ b/inc/Platform/x86/Helpers/UTCTimestamp.hpp
@@ -0,0 +1,5 @@
+#include <Time/UTCTimestamp.hpp>
+#include <ostream>
+#include <iomanip>
+
+std::ostream& operator<<(std::ostream& o, UTCTimestamp const& date);
diff --git a/inc/Time/Time.hpp b/inc/Time/Time.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..55dbaa16ec64dfac8a811d7b05ea719296c1e7b4
--- /dev/null
+++ b/inc/Time/Time.hpp
@@ -0,0 +1,268 @@
+#pragma once
+#include <cstdint>
+#include <Message.hpp>
+#include "macros.hpp"
+#include "etl/String.hpp"
+
+/**
+ * @defgroup Time Time
+ *
+ * Group of classes, functions and variables to represent and operate on time
+ *
+ * The implementation of the ECSS services requires storing and handling timestamps quite often. However, in an embedded
+ * system with mission-critical tasks, it is wide to avoid the complexity of a full time management system, as the one
+ * that comes with Linux or contains [large timezone databases](https://en.wikipedia.org/wiki/Tz_database).
+ *
+ * This repository provides a set of classes and utilities that allow handling of on-board spacecraft time. Most formats
+ * are compatible to the [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf) specification, and are
+ * customisable to support different precisions, lengths and durations. Additionally, a number of formats and headers
+ * are provided so that timestamps can be transmitted and received from the Ground Station.
+ *
+ * The base timestamp class representing an instant in time is @ref TimeStamp. This is used as the internal
+ * representation of all moments in this repository. Additional formats are provided for convenience and compatibility
+ * with other systems:
+ *  - @ref UTCTimestamp
+ *
+ * You can convert between different types of timestamp, either by using constructors, or conversion functions
+ * defined in each timestamp class.
+ *
+ * Note that you should try sticking to the default @ref TimeStamp class unless needed, as it provides the best
+ * precision and performance for the least amount of memory, and is directly compatible with internal timestamps
+ * used, without loss of precision.
+ *
+ * @section Epoch Epoch
+ *
+ * Internal timestamp representations can use a single point in time referring to $t=0$. This point is referred to as
+ * the **Epoch** and is stored in @ref Time::Epoch. CSSDS suggests using _1 January 1958_ as the Epoch. UNIX uses _1
+ * January 1970_. In this repository however, each implementor can choose a different epoch (e.g. the start of the
+ * mission).
+ *
+ * @section DevUtils Developer utilities
+ * Timestamp classes are designed to make the developer's life easier.
+ *
+ * You can easily compare timestamps without having to call any other functions:
+ * @code
+ * if (timestamp1 < timestamp2)
+ * @endcode
+ *
+ * If you are building on x86, you can directly print UTC timestamps:
+ * @code
+ * std::cout << utcTimestamp << std::endl;
+ * @endcode
+ *
+ * @section UTC UTC and Leap seconds
+ * All internal timestamps are represented in the GMT+00:00 timezone due to the high expense of timezone
+ * calculations.
+ *
+ * This implementation uses **TAI (International Atomic Time)** instead of UTC (Coordinated Universal Time).
+ * [TAI](https://en.wikipedia.org/wiki/International_Atomic_Time) is equivalent to UTC, with the exception of leap
+ * seconds, which are occasionally added to match terrestrial and astronomical time. As leap seconds are added after
+ * human intervention to UTC, it is impossible for a satellite to know the exact UTC time without ground station
+ * intervention.
+ */
+
+/**
+ * An armada of utilities regarding timekeeping, timestamps and conversion between different internal and string
+ * time formats. This file implements [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf).
+ *
+ * @ingroup Time
+ * @author Baptiste Fournier
+ */
+namespace Time
+{
+inline constexpr uint8_t SecondsPerMinute = 60;
+inline constexpr uint16_t SecondsPerHour = 3600;
+inline constexpr uint32_t SecondsPerDay = 86400;
+static constexpr uint8_t DaysOfMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+/**
+ * Number of bytes used for the basic time units of the CUC header for this mission
+ */
+inline constexpr uint8_t CUCSecondsBytes = 2;
+
+/**
+ * Number of bytes used for the fractional time units of the CUC header for this mission
+ */
+inline constexpr uint8_t CUCFractionalBytes = 2;
+
+/**
+ * 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 {
+	uint16_t year;
+	uint8_t month;
+	uint8_t day;
+} Epoch{
+    2020,
+    1,
+    1,
+};
+
+/**
+ * Number of seconds elapsed between the UNIX epoch (1 January 1970) and the system epoch.
+ *
+ * The system epoch is defined by @ref Epoch.
+ * This constant is used for conversion between Unix and other timestamps.
+ * Leap seconds are not taken into account here.
+ *
+ * @warning This value MUST be updated after every change of the system @ref Epoch. You can use utilities such as
+ * https://www.unixtimestamp.com/ to obtain a correct result.
+ */
+inline constexpr uint32_t EpochSecondsFromUnix = 1577836800;
+
+/**
+ * The maximum theoretical size in bytes of a CUC timestamp, including headers (P-field and T-field)
+ */
+inline constexpr uint8_t CUCTimestampMaximumSize = 9;
+
+static_assert(Epoch.year >= 2019);
+static_assert(Epoch.month < 11 && Epoch.month >= 0);
+static_assert(Epoch.day < DaysOfMonth[Epoch.month]);
+
+/**
+ * Builds the short P-field of the CUC (CCSDS Unsegmented Time Code) format, as defined in CCSDS 301.0-B-4.
+ *
+ * The short P-field contains only one byte. It is used when many octets are used to represent the basic or fractional
+ * time units.
+ *
+ * @see CCSDS 301.0-B-4, Section 3.2.2
+ * @tparam secondsBytes The number of octets used to represent the basic time units
+ * @tparam fractionalBytes The number of octets used to represent the fractional time units
+ * @return A single byte, representing the P-field contents
+ */
+template <int secondsBytes, int fractionalBytes>
+inline constexpr uint8_t buildShortCUCHeader() {
+	static_assert(secondsBytes <= 4, "Use buildLongCUCHeader instead");
+	static_assert(fractionalBytes <= 3, "Use buildLongCUCHeader instead");
+
+	uint8_t header = 0;
+
+	// P-Field extension is 0, CUC header is not extended
+	header += 0;
+
+	// We are using a custom TAI epoch ("agency-defined epoch")
+	header <<= 3U;
+	header += 0b010;
+
+	// Number of bytes in the basic time unit
+	header <<= 2U;
+	header += secondsBytes - 1;
+
+	// Number of bytes in the fractional unit
+	header <<= 2U;
+	header += fractionalBytes;
+
+	return header;
+}
+
+/**
+ * Builds the long P-field of the CUC (CCSDS Unsegmented Time Code) format, as defined in CCSDS 301.0-B-4.
+ *
+ * The long P-field contains two bytes. The 2nd byte is used to define the size of the additional octets added to the
+ * timestamp, which could not fit in a short P-field.
+ *
+ * @see CCSDS 301.0-B-4, Section 3.2.2
+ * @tparam secondsBytes The number of octets used to represent the basic time units
+ * @tparam fractionalBytes The number of octets used to represent the fractional time units
+ * @return Two bytes, representing the P-field contents
+ */
+template <int secondsBytes, int fractionalBytes>
+inline constexpr uint16_t buildLongCUCHeader() {
+	// cppcheck-suppress redundantCondition
+	static_assert(secondsBytes > 4 || fractionalBytes > 3, "Use buildShortCUCHeader instead");
+	static_assert(secondsBytes <= 7, "Number of bytes for seconds over maximum number of octets allowed by CCSDS");
+	static_assert(fractionalBytes <= 6, "Number of bytes for seconds over maximum number of octets allowed by CCSDS");
+
+	uint16_t header = 0;
+
+	uint8_t octet1secondsBytes = std::min(4, secondsBytes);
+	uint8_t octet2secondsBytes = secondsBytes - octet1secondsBytes;
+
+	uint8_t octet1fractionalBytes = std::min(3, fractionalBytes);
+	uint8_t octet2fractionalBytes = fractionalBytes - octet1fractionalBytes;
+
+	// P-Field extension is 1, CUC header is extended
+	header += 1;
+
+	// We are using custom a TAI epoch
+	header <<= 3U;
+	header += 0b010;
+
+	// Number of bytes in the basic time unit
+	header <<= 2U;
+	header += octet1secondsBytes - 1;
+
+	// Number of bytes in the fractional unit
+	header <<= 2U;
+	header += octet1fractionalBytes;
+
+	// P-Field extension is 1, CUC header was extended
+	header <<= 1U;
+	header += 1;
+
+	// Number of bytes in the extended basic time unit
+	header <<= 2U;
+	header += octet2secondsBytes;
+
+	// Number of bytes in the extended fractional unit
+	header <<= 3U;
+	header += octet2fractionalBytes;
+
+	// Last 3 LSB are reserved for custom mission use
+	header <<= 2U;
+	header += 0;
+
+	return header;
+}
+
+/**
+ * Builds the entire P-field of the CUC (CCSDS Unsegmented Time Code) format, as defined in CCSDS 301.0-B-4.
+ *
+ * The P-field contains the metadata of the timestamp, including information about its size and epoch. This function
+ * is implemented for arbitrary sizes (_octet count_) for the basic and fractional time units.
+ *
+ * The following options cannot be changed:
+ *   - Time-code identification is set to an _agency-defined epoch_. This is represented by @ref Epoch.
+ *   - Bits 6-7 of octet 2 (_reserved_) are set to `0`
+ *
+ * @note The P-field (header) does not contain the timestamp information, but only the description of the timestamp's
+ * structure. It may be entirely omitted if the structure is known beforehand.
+ *
+ * @see CCSDS 301.0-B-4, Section 3.2.2
+ * @tparam T An arbitrary `uint` return type of the header
+ * @tparam secondsBytes The number of octets used to represent the basic time units
+ * @tparam fractionalBytes The number of octets used to represent the fractional time units
+ * @return One or two bytes representing the header
+ */
+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
+	if constexpr (secondsBytes <= 4 && fractionalBytes <= 3) {
+		return buildShortCUCHeader<secondsBytes, fractionalBytes>();
+	} else {
+		return buildLongCUCHeader<secondsBytes, fractionalBytes>();
+	}
+}
+
+/**
+ * Returns whether a year is a leap year according to the Gregorian calendar
+ */
+constexpr bool isLeapYear(uint16_t year) {
+	if ((year % 4) != 0) {
+		return false;
+	}
+	if ((year % 100) != 0) {
+		return true;
+	}
+	return (year % 400) == 0;
+}
+
+typedef struct {
+	uint64_t elapsed100msTicks = 0;
+} CustomCUC_t;
+
+} // namespace Time
diff --git a/inc/Time/TimeStamp.hpp b/inc/Time/TimeStamp.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..499fb3c66ac9869be384e9fde471e4eb903d5c05
--- /dev/null
+++ b/inc/Time/TimeStamp.hpp
@@ -0,0 +1,168 @@
+#ifndef ECSS_SERVICES_TIME_HPP
+#define ECSS_SERVICES_TIME_HPP
+
+#include <cstdint>
+#include <algorithm>
+#include "macros.hpp"
+#include <etl/array.h>
+#include "Time.hpp"
+#include "UTCTimestamp.hpp"
+
+/**
+ * A class that represents an instant in time, with convenient conversion
+ * to and from usual time and date representations
+ *
+ * @note
+ * This class uses internally TAI time, and handles UTC leap seconds at conversion to and
+ * from UTC time system.
+ *
+ * @ingroup Time
+ * @author Baptiste Fournier
+ * @see [CCSDS 301.0-B-4](https://public.ccsds.org/Pubs/301x0b4e1.pdf)
+ */
+template <uint8_t secondsBytes, uint8_t fractionalBytes>
+class TimeStamp {
+private:
+	static_assert(secondsBytes + fractionalBytes <= 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;
+
+	/**
+	 * 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
+	 * part is included as the least significant bits of this variable, and the base part follows.
+	 */
+	TAICounter_t taiCounter;
+
+	/**
+	 * 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>();
+
+	/**
+	 * 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
+	 */
+	static constexpr uint64_t maxSecondCounterValue = (1U << (8U * secondsBytes)) - 1;
+
+	/**
+	 * Returns whether the amount of `seconds` can be represented by this TimeStamp.
+	 * If `seconds` is too large, the number of `secondsByte` may not be enough to represent this timestamp.
+	 *
+	 * @param seconds The amount of seconds from @ref Time::Epoch
+	 */
+	static constexpr bool areSecondsValid(TAICounter_t seconds);
+
+public:
+	/**
+	 * Initialize the TimeStamp at @ref Time::Epoch
+	 */
+	TimeStamp() : taiCounter(0){};
+
+	/**
+	 * Initialize the TimeStamp from a duration from epoch in TAI (leap seconds not accounted)
+	 *
+	 * @param taiSecondsFromEpoch An integer number of seconds from the custom @ref Time::Epoch
+	 */
+	explicit TimeStamp(uint64_t taiSecondsFromEpoch);
+
+	/**
+	 * Initialize the TimeStamp from a count of 100ms ticks from epoch in TAI (leap seconds not accounted)
+	 *
+	 * @param customCUCTimestamp An struct containing a 64 bit unsigned number of 100ms
+	 * ticks from the custom @ref Time::Epoch
+	 */
+	explicit TimeStamp(Time::CustomCUC_t customCUCTimestamp);
+
+	/**
+	 * 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
+	 */
+	explicit TimeStamp(etl::array<uint8_t, Time::CUCTimestampMaximumSize> timestamp);
+
+	/**
+	 * Initialize the Timestamp from a UTC timestamp struct
+	 *
+	 * @param timestamp a UTC timestamp, from Unix Epoch
+	 */
+	explicit TimeStamp(const UTCTimestamp& timestamp);
+
+	/**
+	 * Get the representation as seconds from epoch in TAI
+	 *
+	 * @return The seconds elapsed in TAI since @ref Time::Epoch. This function is explicitly defined
+	 */
+	TAICounter_t asTAIseconds();
+
+	/**
+	 * Get the representation as a struct containing 100ms ticks from epoch in TAI
+	 *
+	 * @return An struct containing a 64 bit unsigned number of 100ms
+	 * ticks from the custom @ref Time::Epoch. This function is explicitly defined.
+	 */
+	Time::CustomCUC_t asCustomCUCTimestamp();
+
+	/**
+	 * 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();
+
+	/**
+	 * Get the representation as CUC formatted bytes
+	 *
+	 * @return The TimeStamp, represented in the CCSDS CUC format
+	 */
+	etl::array<uint8_t, Time::CUCTimestampMaximumSize> toCUCtimestamp();
+
+	/**
+	 * Get the representation as a UTC timestamp
+	 *
+	 * @return The TimeStamp, represented in the structure that holds UTC timestamps
+	 */
+	UTCTimestamp toUTCtimestamp();
+
+	/**
+	 * Compare two timestamps.
+	 *
+	 * @param timestamp the date that will be compared with the pointer `this`
+	 * @return true if the condition is satisfied
+	 */
+	bool operator<(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const {
+		return taiCounter < timestamp.taiCounter;
+	}
+
+	bool operator>(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const {
+		return taiCounter > timestamp.taiCounter;
+	}
+
+	bool operator==(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const {
+		return taiCounter == timestamp.taiCounter;
+	}
+
+	bool operator!=(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const {
+		return taiCounter != timestamp.taiCounter;
+	}
+
+	bool operator<=(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const {
+		return taiCounter <= timestamp.taiCounter;
+	}
+
+	bool operator>=(const TimeStamp<secondsBytes, fractionalBytes>& timestamp) const {
+		return taiCounter >= 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
new file mode 100644
index 0000000000000000000000000000000000000000..34edf5a2c291153b1a38778ebc0462ec661d9fd6
--- /dev/null
+++ b/inc/Time/TimeStamp.tpp
@@ -0,0 +1,187 @@
+template <uint8_t secondsBytes, uint8_t fractionalBytes>
+constexpr bool TimeStamp<secondsBytes, fractionalBytes>::areSecondsValid(TimeStamp::TAICounter_t seconds) {
+	return seconds < maxSecondCounterValue;
+}
+
+template <uint8_t secondsBytes, uint8_t fractionalBytes>
+TimeStamp<secondsBytes, fractionalBytes>::TimeStamp(uint64_t taiSecondsFromEpoch) {
+	ASSERT_INTERNAL(areSecondsValid((taiSecondsFromEpoch)), ErrorHandler::InternalErrorType::InvalidTimeStampInput);
+
+	taiCounter = static_cast<TAICounter_t>(taiSecondsFromEpoch) << 8 * fractionalBytes;
+}
+
+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 secondsCounter, uint8_t fractionalBytes>
+TimeStamp<secondsCounter, fractionalBytes>::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;
+
+	if (headerSize == 2) {
+		inputSecondsBytes += (timestamp[1] & 0b01100000U) >> 5U;
+		inputFractionalBytes += (timestamp[1] & 0b00011100U) >> 2U;
+	}
+
+	// check input validity (useless bytes set to 0)
+	for (int i = headerSize + inputSecondsBytes + inputFractionalBytes; i < Time::CUCTimestampMaximumSize; i++) {
+		if (timestamp[i] != 0) {
+			ErrorHandler::reportInternalError(ErrorHandler::InternalErrorType::InvalidTimeStampInput);
+			break;
+		}
+	}
+
+	// do checks wrt template precision parameters
+	ASSERT_INTERNAL(inputSecondsBytes <= secondsCounter, ErrorHandler::InternalErrorType::InvalidTimeStampInput);
+	ASSERT_INTERNAL(inputFractionalBytes <= fractionalBytes, 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++) {
+		taiCounter = taiCounter << 8;
+		taiCounter += timestamp[headerSize + i];
+	}
+	// pad rightmost bytes to full length
+	taiCounter = taiCounter << 8 * (fractionalBytes - inputFractionalBytes);
+}
+
+template <uint8_t seconds_counter_bytes, uint8_t fractional_counter_bytes>
+TimeStamp<seconds_counter_bytes, fractional_counter_bytes>::TimeStamp(const UTCTimestamp& timestamp) {
+	TAICounter_t seconds = 0;
+	for (int year = Time::Epoch.year; year < timestamp.year; ++year) {
+		seconds += (Time::isLeapYear(year) ? 366 : 365) * Time::SecondsPerDay;
+	}
+	for (int month = Time::Epoch.month; month < timestamp.month; ++month) {
+		seconds += Time::DaysOfMonth[month - 1] * Time::SecondsPerDay;
+		if ((month == 2U) && Time::isLeapYear(timestamp.year)) {
+			seconds += 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;
+}
+
+template <uint8_t secondsBytes, uint8_t fractionalBytes>
+typename TimeStamp<secondsBytes, fractionalBytes>::TAICounter_t
+TimeStamp<secondsBytes, fractionalBytes>::asTAIseconds() {
+	return taiCounter >> (8 * fractionalBytes);
+}
+
+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 secondsBytes, uint8_t fractionalBytes>
+template <typename T>
+T TimeStamp<secondsBytes, fractionalBytes>::asTAIseconds() {
+	static_assert(std::is_floating_point_v<T>, "TimeStamp::asTAIseconds() only accepts numeric types.");
+	static_assert(std::numeric_limits<T>::max() >= maxSecondCounterValue);
+
+	TAICounter_t decimalPart = taiCounter >> (8 * fractionalBytes);
+
+	T fractionalPart = taiCounter - (decimalPart << (8 * fractionalBytes));
+	T fractionalPartMax = (1U << (8U * fractionalBytes)) - 1U;
+
+	return decimalPart + fractionalPart / fractionalPartMax;
+}
+
+template <uint8_t secondsBytes, uint8_t fractionalBytes>
+etl::array<uint8_t, Time::CUCTimestampMaximumSize> TimeStamp<secondsBytes, fractionalBytes>::toCUCtimestamp() {
+	etl::array<uint8_t, Time::CUCTimestampMaximumSize> returnArray = {0};
+
+	static constexpr uint8_t headerBytes = (secondsBytes < 4 && fractionalBytes < 3) ? 1 : 2;
+
+	if (headerBytes == 1) {
+		returnArray[0] = static_cast<uint8_t>(CUCHeader);
+	} else {
+		returnArray[1] = static_cast<uint8_t>(CUCHeader);
+		returnArray[0] = static_cast<uint8_t>(CUCHeader >> 8);
+	}
+
+	for (auto byte = 0; byte < secondsBytes + fractionalBytes; byte++) {
+		uint8_t taiCounterIndex = 8 * (secondsBytes + fractionalBytes - byte - 1);
+		returnArray[headerBytes + byte] = taiCounter >> taiCounterIndex;
+	}
+
+	return returnArray;
+}
+
+template <uint8_t secondsBytes, uint8_t fractionalBytes>
+UTCTimestamp TimeStamp<secondsBytes, fractionalBytes>::toUTCtimestamp() {
+	using namespace Time;
+
+	uint32_t totalSeconds = asTAIseconds();
+
+	uint16_t yearUTC = Epoch.year;
+	uint8_t monthUTC = Epoch.month;
+	uint8_t dayUTC = Epoch.day;
+	uint8_t hour = 0;
+	uint8_t minute = 0;
+	uint8_t second = 0;
+
+	// calculate years
+	while (totalSeconds >= (isLeapYear(yearUTC) ? 366 : 365) * SecondsPerDay) {
+		totalSeconds -= (isLeapYear(yearUTC) ? 366 : 365) * SecondsPerDay;
+		yearUTC++;
+	}
+
+	// calculate months
+	int currentMonth = 0;
+	while (totalSeconds >= (DaysOfMonth[currentMonth] * SecondsPerDay)) {
+		monthUTC++;
+		totalSeconds -= (DaysOfMonth[currentMonth] * SecondsPerDay);
+		currentMonth++;
+		if ((currentMonth == 1U) && isLeapYear(yearUTC)) {
+			if (totalSeconds <= (28 * SecondsPerDay)) {
+				break;
+			}
+			monthUTC++;
+			totalSeconds -= 29 * SecondsPerDay;
+			currentMonth++;
+		}
+	}
+
+	dayUTC = totalSeconds / SecondsPerDay;
+	totalSeconds -= dayUTC * SecondsPerDay;
+	dayUTC++; // add 1 day because we start count from 1 January (and not 0 January!)
+
+	hour = totalSeconds / SecondsPerHour;
+	totalSeconds -= hour * SecondsPerHour;
+
+	minute = totalSeconds / SecondsPerMinute;
+	totalSeconds -= minute * SecondsPerMinute;
+
+	second = totalSeconds;
+
+	return {yearUTC, monthUTC, dayUTC, hour, minute, second};
+}
diff --git a/inc/Time/UTCTimestamp.hpp b/inc/Time/UTCTimestamp.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..062402df0926efcf9d739cadd7182ca1710a8fe2
--- /dev/null
+++ b/inc/Time/UTCTimestamp.hpp
@@ -0,0 +1,60 @@
+#pragma once
+
+#include <cstdint>
+#include <etl/String.hpp>
+
+/**
+ * A class that represents a UTC time and date according to ISO 8601
+ *
+ * This class contains a human-readable representation of a timestamp, accurate down to 1 second. It is not used
+ * for timestamp storage in the satellite due to its high performance and memory cost, but it can be used for
+ * debugging and logging purposes.
+ *
+ * @ingroup Time
+ * @note
+ * This class represents UTC (Coordinated Universal Time) date
+ */
+class UTCTimestamp {
+public:
+	uint16_t year;
+	uint8_t month;
+	uint8_t day;
+	uint8_t hour;
+	uint8_t minute;
+	uint8_t second;
+
+	/**
+	 * Initialise a timestamp with the Unix epoch 1/1/1970 00:00:00
+	 */
+	UTCTimestamp();
+
+	/**
+	 *
+	 * @todo See if this implements leap seconds
+	 * @todo Implement leap seconds as ST[20] parameter
+	 * @param year the year as it used in Gregorian calendar
+	 * @param month the month as it used in Gregorian calendar (1-12 inclusive)
+	 * @param day the day as it used in Gregorian calendar (1-31 inclusive)
+	 * @param hour UTC hour in 24-hour format
+	 * @param minute UTC minutes
+	 * @param second UTC seconds
+	 */
+	UTCTimestamp(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second);
+
+	/**
+	 * @param textTimestamp the timestamp to parse into a UTC date
+	 * @todo Too expensive to implement (?). It is better to remove this and open it as another issue, or create
+	 * a platform-specific converter that will be only used in x86.
+	 */
+	explicit UTCTimestamp(etl::string<32> textTimestamp);
+
+	/**
+	 * Compare two timestamps.
+	 * @param Date the date that will be compared with the pointer `this`
+	 */
+	bool operator<(const UTCTimestamp& Date) const;
+	bool operator>(const UTCTimestamp& Date) const; ///< @copydoc UTCTimestamp::operator<
+	bool operator==(const UTCTimestamp& Date) const; ///< @copydoc UTCTimestamp::operator<
+	bool operator<=(const UTCTimestamp& Date) const; ///< @copydoc UTCTimestamp::operator<
+	bool operator>=(const UTCTimestamp& Date) const; ///< @copydoc UTCTimestamp::operator<
+};
diff --git a/src/Platform/x86/Time/UTCTimestamp.cpp b/src/Platform/x86/Time/UTCTimestamp.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c48aecd4f00d8e2a356c443bf933236d15c8052c
--- /dev/null
+++ b/src/Platform/x86/Time/UTCTimestamp.cpp
@@ -0,0 +1,12 @@
+#include <Time/UTCTimestamp.hpp>
+#include <ostream>
+#include <iomanip>
+
+std::ostream& operator<<(std::ostream& o, UTCTimestamp const& date) {
+	// YYYY-MM-DDTHH:mm:ss.sssZ
+	o.fill('0');
+	o << static_cast<int>(date.year) << "-" << std::setw(2) << static_cast<int>(date.month) << "-" << std::setw(2)
+	  << static_cast<int>(date.day) << "T" << std::setw(2) << static_cast<int>(date.hour) << ":" << std::setw(2)
+	  << static_cast<int>(date.minute) << ":" << std::setw(2) << static_cast<int>(date.second) << ":000Z" << std::endl;
+	return o;
+}
diff --git a/src/Platform/x86/main.cpp b/src/Platform/x86/main.cpp
index 90f36f8b4f5869182d9cfc78c1546837186caa66..016647fda0cf1e2142123300802652a6b2899222 100644
--- a/src/Platform/x86/main.cpp
+++ b/src/Platform/x86/main.cpp
@@ -1,5 +1,7 @@
 #include <iostream>
 #include <Logger.hpp>
+#include <Time/UTCTimestamp.hpp>
+#include <Platform/x86/Helpers/UTCTimestamp.hpp>
 #include "Helpers/CRCHelper.hpp"
 #include "Services/TestService.hpp"
 #include "Services/ParameterService.hpp"
@@ -360,5 +362,7 @@ int main() {
 	timeBasedSchedulingService.summaryReportActivitiesByID(receivedMsg);
 
 	LOG_NOTICE << "ECSS Services test complete";
+
+	std::cout << UTCTimestamp() << std::endl;
 	return 0;
 }
diff --git a/src/Helpers/TimeFormats.cpp b/src/Time/UTCTimestamp.cpp
similarity index 60%
rename from src/Helpers/TimeFormats.cpp
rename to src/Time/UTCTimestamp.cpp
index a699de5ee859949d4586683ffa19fdea33ec8872..072d6b67ff32e1db02d1a835a6ae61c3d43d9a1f 100644
--- a/src/Helpers/TimeFormats.cpp
+++ b/src/Time/UTCTimestamp.cpp
@@ -1,29 +1,11 @@
-#include "Helpers/TimeFormats.hpp"
+#include "Time/Time.hpp"
+#include "Time/UTCTimestamp.hpp"
 #include <iomanip>
 
-bool is_leap_year(uint16_t year) {
-	if ((year % 4) != 0) {
-		return false;
-	}
-	if ((year % 100) != 0) {
-		return true;
-	}
-	return (year % 400) == 0;
-}
+UTCTimestamp::UTCTimestamp() : year(1970), month(1), second(0), minute(0), hour(0), day(1) {}
 
-UTC_Timestamp::UTC_Timestamp() {
-	// Unix epoch 1/1/1970
-	year = 1970;
-	month = 1;
-	day = 1;
-	hour = 0;
-	minute = 0;
-	second = 0;
-}
-
-UTC_Timestamp::UTC_Timestamp(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) {
-	// check if the parameters make sense
-	ASSERT_INTERNAL(2019 <= year, ErrorHandler::InternalErrorType::InvalidDate);
+UTCTimestamp::UTCTimestamp(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) {
+	ASSERT_INTERNAL(1970 <= year, ErrorHandler::InternalErrorType::InvalidDate);
 	ASSERT_INTERNAL((1 <= month) && (month <= 12), ErrorHandler::InternalErrorType::InvalidDate);
 	ASSERT_INTERNAL((1 <= day) && (day <= 31), ErrorHandler::InternalErrorType::InvalidDate);
 	ASSERT_INTERNAL(hour < 24, ErrorHandler::InternalErrorType::InvalidDate);
@@ -39,8 +21,7 @@ UTC_Timestamp::UTC_Timestamp(uint16_t year, uint8_t month, uint8_t day, uint8_t
 	this->second = second;
 }
 
-bool UTC_Timestamp::operator<(const UTC_Timestamp& Date) {
-	// compare years
+bool UTCTimestamp::operator<(const UTCTimestamp& Date) const {
 	if (this->year < Date.year) {
 		return true;
 	}
@@ -48,7 +29,6 @@ bool UTC_Timestamp::operator<(const UTC_Timestamp& Date) {
 		return false;
 	}
 
-	// compare months
 	if (this->month < Date.month) {
 		return true;
 	}
@@ -56,7 +36,6 @@ bool UTC_Timestamp::operator<(const UTC_Timestamp& Date) {
 		return false;
 	}
 
-	// compare days
 	if (this->day < Date.day) {
 		return true;
 	}
@@ -64,7 +43,6 @@ bool UTC_Timestamp::operator<(const UTC_Timestamp& Date) {
 		return false;
 	}
 
-	// compare hours
 	if (this->hour < Date.hour) {
 		return true;
 	}
@@ -72,7 +50,6 @@ bool UTC_Timestamp::operator<(const UTC_Timestamp& Date) {
 		return false;
 	}
 
-	// compare minutes
 	if (this->minute < Date.minute) {
 		return true;
 	}
@@ -80,7 +57,6 @@ bool UTC_Timestamp::operator<(const UTC_Timestamp& Date) {
 		return false;
 	}
 
-	// compare seconds
 	if (this->second < Date.second) {
 		return true;
 	}
@@ -88,7 +64,7 @@ bool UTC_Timestamp::operator<(const UTC_Timestamp& Date) {
 	return false;
 }
 
-bool UTC_Timestamp::operator>(const UTC_Timestamp& Date) {
+bool UTCTimestamp::operator>(const UTCTimestamp& Date) const {
 	if (this->year > Date.year) {
 		return true;
 	}
@@ -126,7 +102,7 @@ bool UTC_Timestamp::operator>(const UTC_Timestamp& Date) {
 	return false;
 }
 
-bool UTC_Timestamp::operator==(const UTC_Timestamp& Date) {
+bool UTCTimestamp::operator==(const UTCTimestamp& Date) const {
 	if (this->year != Date.year) {
 		return false;
 	}
@@ -148,19 +124,10 @@ bool UTC_Timestamp::operator==(const UTC_Timestamp& Date) {
 	return true;
 }
 
-bool UTC_Timestamp::operator<=(const UTC_Timestamp& Date) {
+bool UTCTimestamp::operator<=(const UTCTimestamp& Date) const {
 	return ((*this < Date) || (*this == Date));
 }
 
-bool UTC_Timestamp::operator>=(const UTC_Timestamp& Date) {
+bool UTCTimestamp::operator>=(const UTCTimestamp& Date) const {
 	return ((*this > Date) || (*this == Date));
 }
-
-std::ostream& operator<<(std::ostream& o, UTC_Timestamp const& Date) { // NOLINT
-	// YYYY-MM-DDTHH:mm:ss.sssZ
-	o.fill('0');
-	o << static_cast<int>(Date.year) << "-" << std::setw(2) << static_cast<int>(Date.month) << "-" << std::setw(2)
-	  << static_cast<int>(Date.day) << "T" << std::setw(2) << static_cast<int>(Date.hour) << ":" << std::setw(2)
-	  << static_cast<int>(Date.minute) << ":" << std::setw(2) << static_cast<int>(Date.second) << ":000Z" << std::endl;
-	return o;
-}
diff --git a/test/Helpers/TimeFormats.cpp b/test/Helpers/TimeFormats.cpp
deleted file mode 100644
index c6562503f6f20fbf3e210623e0226e7705822734..0000000000000000000000000000000000000000
--- a/test/Helpers/TimeFormats.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "catch2/catch.hpp"
-#include "Helpers/TimeFormats.hpp"
-#include "../Services/ServiceTests.hpp"
-
-TEST_CASE("UTC timestamps") {
-	// invalid year
-	UTC_Timestamp Timestamp0{2018, 4, 10, 10, 15, 0};
-
-	// invalid month
-	UTC_Timestamp Timestamp1{2020, 60, 10, 10, 15, 0};
-
-	// invalid day
-	UTC_Timestamp Timestamp2{2020, 4, 35, 10, 15, 0};
-
-	// invalid hour
-	UTC_Timestamp Timestamp3{2020, 4, 10, 100, 15, 0};
-
-	// invalid minute
-	UTC_Timestamp Timestamp4{2020, 4, 10, 10, 200, 0};
-
-	// invalid second
-	UTC_Timestamp Timestamp5{2020, 4, 10, 10, 15, 100};
-
-	CHECK(ServiceTests::countErrors() == 6);
-	CHECK(ServiceTests::thrownError(ErrorHandler::InvalidDate));
-}
diff --git a/test/Helpers/TimeStamp.cpp b/test/Helpers/TimeStamp.cpp
deleted file mode 100644
index 4202cb166cd78afd21d9f10103979e4866db2d92..0000000000000000000000000000000000000000
--- a/test/Helpers/TimeStamp.cpp
+++ /dev/null
@@ -1,128 +0,0 @@
-#include "catch2/catch.hpp"
-#include "Helpers/TimeStamp.hpp"
-#include "../Services/ServiceTests.hpp"
-#include <typeinfo>
-#include <iostream>
-
-TEST_CASE("TimeStamp class construction") {
-	// SECTION("Initialize with excessive precision, breaks at compile time"){
-	//   TimeStamp<5, 10> Epoch3;
-	//   TimeStamp<4, 4> Epoch4;
-	// }
-
-	SECTION("Test CUC headers generation") {
-		uint8_t cuc_header1 = build_CUC_header<uint8_t, 2, 2>();
-		CHECK(cuc_header1 == 0b00100110);
-
-		uint8_t cuc_header2 = build_CUC_header<uint8_t, 4, 1>();
-		CHECK(cuc_header2 == 0b00101101);
-
-		uint8_t cuc_header3 = build_CUC_header<uint8_t, 1, 1>();
-		CHECK(cuc_header3 == 0b00100001);
-
-		uint16_t cuc_header4 = build_CUC_header<uint16_t, 5, 1>();
-		CHECK(cuc_header4 == 0b1010110110100000);
-
-		uint16_t cuc_header5 = build_CUC_header<uint16_t, 1, 6>();
-		CHECK(cuc_header5 == 0b1010001110001100);
-
-		uint16_t cuc_header6 = build_CUC_header<uint16_t, 7, 1>();
-		CHECK(cuc_header6 == 0b1010110111100000);
-	}
-
-	SECTION("Check TAI idempotence") {
-		int input_time = 1000;
-		TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(input_time);
-		REQUIRE(Epoch.as_TAI_seconds() == input_time); // check initialization has intended effect
-	}
-
-	SECTION("Check CUC idempotence") {
-		etl::array<uint8_t, 9> input_time1 = {0b00100110, 0, 1, 1, 3, 0, 0, 0, 0};
-		TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch1(input_time1);
-		etl::array<uint8_t, 9> test_return1 = Epoch1.as_CUC_timestamp();
-
-		for (auto i = 0; i < 9; i++) {
-			bool test = input_time1[i] == test_return1[i];
-			REQUIRE(test);
-		}
-
-		etl::array<uint8_t, 9> input_time2 = {0b10101101, 0b10100000, 218, 103, 11, 0, 3, 23, 0};
-		TimeStamp<5, 1> Epoch2(input_time2);
-		etl::array<uint8_t, 9> test_return2 = Epoch2.as_CUC_timestamp();
-
-		for (auto i = 0; i < 9; i++) {
-			bool test = input_time2[i] == test_return2[i];
-			REQUIRE(test);
-		}
-
-		etl::array<uint8_t, 9> input_time3 = {0b10100011, 0b10001100, 218, 103, 11, 0, 3, 23, 2};
-		TimeStamp<1, 6> Epoch3(input_time3);
-		etl::array<uint8_t, 9> test_return3 = Epoch3.as_CUC_timestamp();
-
-		for (auto i = 0; i < 9; i++) {
-			bool test = input_time3[i] == test_return3[i];
-			REQUIRE(test);
-		}
-	}
-
-	SECTION("Check UTC idempotence") {
-		{
-			UTC_Timestamp timestamp1(2020, 4, 10, 10, 15, 0); // 10 Apr 2020, 10:15:00;
-			TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(timestamp1);
-			UTC_Timestamp timestamp2 = Epoch.as_UTC_timestamp();
-			bool cond = (timestamp2 == timestamp1);
-			REQUIRE(cond);
-		}
-		{
-			UTC_Timestamp timestamp1(2035, 1, 1, 0, 0, 1); // 1 Jan 2035 midnight passed;
-			TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(timestamp1);
-			UTC_Timestamp timestamp2 = Epoch.as_UTC_timestamp();
-			bool cond = (timestamp2 == timestamp1);
-			REQUIRE(cond);
-		}
-	}
-
-	SECTION("UTC conversion to and from seconds timestamps") {
-		{
-			UTC_Timestamp timestamp1(2020, 12, 5, 0, 0, 0); // 5 Dec 2020, midnight;
-			TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(timestamp1);
-			REQUIRE(Epoch.as_TAI_seconds() == 1607126400);
-		}
-		{
-			UTC_Timestamp timestamp1(2020, 2, 29, 0, 0, 0); // 5 Dec 2020, midnight;
-			TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(timestamp1);
-			REQUIRE(Epoch.as_TAI_seconds() == 1582934400);
-		}
-	}
-
-	// SECTION("Check different templates, should break at compile"){
-	//   TimeStamp<1, 2> Epoch1;
-	//   TimeStamp<4, 4> Epoch2;
-	//   REQUIRE(Epoch1==Epoch2);
-	// }
-
-	SECTION("Check operators") {
-		TimeStamp<1, 2> Epoch1;
-		TimeStamp<1, 2> Epoch2;
-		TimeStamp<1, 2> Epoch3(10);
-		TimeStamp<1, 2> Epoch4(15);
-		TimeStamp<1, 2> Epoch5(12);
-		TimeStamp<1, 2> Epoch6(10);
-		TimeStamp<2, 2> Epoch7;
-		REQUIRE(Epoch1 == Epoch2);
-		REQUIRE(Epoch2 == Epoch1);
-		REQUIRE(Epoch3 == Epoch6);
-		REQUIRE(Epoch1 != Epoch3);
-		REQUIRE(Epoch3 != Epoch5);
-		REQUIRE(Epoch3 <= Epoch5);
-		REQUIRE(Epoch3 < Epoch5);
-
-		// REQUIRE(Epoch1==Epoch7); //should fail at compile, different templates
-	}
-
-	SECTION("Check runtime class size") {
-		int input_time = 1000;
-		TimeStamp<ACUBESAT_CUC_SECONDS_COUNTER_BYTES, ACUBESAT_CUC_FRACTIONAL_COUNTER_BYTES> Epoch(input_time);
-		REQUIRE(sizeof(Epoch) < 32);
-	}
-}
diff --git a/test/Time/TimeFormats.cpp b/test/Time/TimeFormats.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..883af362b940848bb07bfe4cae5a117fb5178098
--- /dev/null
+++ b/test/Time/TimeFormats.cpp
@@ -0,0 +1,27 @@
+#include "catch2/catch.hpp"
+#include "Time/Time.hpp"
+#include "Time/UTCTimestamp.hpp"
+#include "../Services/ServiceTests.hpp"
+
+TEST_CASE("UTC timestamps") {
+	// invalid year
+	UTCTimestamp Timestamp0{1950, 4, 10, 10, 15, 0};
+
+	// invalid month
+	UTCTimestamp Timestamp1{2020, 60, 10, 10, 15, 0};
+
+	// invalid day
+	UTCTimestamp Timestamp2{2020, 4, 35, 10, 15, 0};
+
+	// invalid hour
+	UTCTimestamp Timestamp3{2020, 4, 10, 100, 15, 0};
+
+	// invalid minute
+	UTCTimestamp Timestamp4{2020, 4, 10, 10, 200, 0};
+
+	// invalid second
+	UTCTimestamp Timestamp5{2020, 4, 10, 10, 15, 100};
+
+	CHECK(ServiceTests::countErrors() == 6);
+	CHECK(ServiceTests::thrownError(ErrorHandler::InvalidDate));
+}
diff --git a/test/Time/TimeStamp.cpp b/test/Time/TimeStamp.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ac99522ba966f817f7a048a615ab4782975007bd
--- /dev/null
+++ b/test/Time/TimeStamp.cpp
@@ -0,0 +1,190 @@
+#include "catch2/catch.hpp"
+#include "Time/TimeStamp.hpp"
+
+using namespace Time;
+
+TEST_CASE("TimeStamp class construction") {
+	// SECTION("Initialize with excessive precision, breaks at compile time"){
+	//   TimeStamp<5, 10> Epoch3;
+	//   TimeStamp<4, 4> Epoch4;
+	// }
+}
+
+TEST_CASE("CUC headers generation") {
+	auto cuc_header1 = buildCUCHeader<uint8_t, 2, 2>();
+	CHECK(cuc_header1 == 0b00100110);
+
+	auto cuc_header2 = buildCUCHeader<uint8_t, 4, 1>();
+	CHECK(cuc_header2 == 0b00101101);
+
+	auto cuc_header3 = buildCUCHeader<uint8_t, 1, 1>();
+	CHECK(cuc_header3 == 0b00100001);
+
+	auto cuc_header4 = buildCUCHeader<uint16_t, 5, 1>();
+	CHECK(cuc_header4 == 0b1010110110100000);
+
+	auto cuc_header5 = buildCUCHeader<uint16_t, 1, 6>();
+	CHECK(cuc_header5 == 0b1010001110001100);
+
+	auto cuc_header6 = buildCUCHeader<uint16_t, 7, 1>();
+	CHECK(cuc_header6 == 0b1010110111100000);
+}
+
+TEST_CASE("TAI idempotence") {
+	int input_time = 1000;
+	TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(input_time);
+
+	REQUIRE(time.asTAIseconds() == input_time);
+}
+
+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();
+
+	for (uint8_t i = 0; i < 9; i++) {
+		CHECK(input1[i] == cuc1[i]);
+	}
+
+	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();
+
+	for (auto i = 0; i < 9; i++) {
+		CHECK(input2[i] == cuc2[i]);
+	}
+
+	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();
+
+	for (auto i = 0; i < 9; i++) {
+		CHECK(input3[i] == cuc3[i]);
+	}
+}
+
+TEST_CASE("Conversion between CUC formats") {
+	SECTION("Base unit conversion") {
+		TimeStamp<2, 2> time1(20123);
+		TimeStamp<5, 2> time2(time1.toCUCtimestamp());
+		CHECK(time1.asTAIseconds() == time2.asTAIseconds());
+	}
+
+	SECTION("Floating unit conversion") {
+		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());
+		CHECK(time1.asTAIseconds() == time2.asTAIseconds());
+	}
+
+	SECTION("All units conversion") {
+		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());
+		CHECK(time1.asTAIseconds() == time2.asTAIseconds());
+	}
+}
+
+TEST_CASE("Use of custom Acubesat CUC format") {
+	SECTION("Check forward conversion") {
+		Time::CustomCUC_t customCUC1 = {1001};
+		TimeStamp<3, 0> time1(customCUC1);
+		CHECK(time1.asTAIseconds() == 100);
+		CHECK(time1.asCustomCUCTimestamp().elapsed100msTicks == 1000);
+		TimeStamp<3, 2> time2(customCUC1);
+		CHECK(time2.asTAIseconds() == 100);
+		CHECK(time2.asCustomCUCTimestamp().elapsed100msTicks == 1000);
+
+		// check rounding errors
+		Time::CustomCUC_t customCUC2 = {1004};
+		TimeStamp<3, 0> time3(customCUC2);
+		CHECK(time3.asTAIseconds() == 100);
+		CHECK(time3.asCustomCUCTimestamp().elapsed100msTicks == 1000);
+		TimeStamp<3, 2> time4(customCUC2);
+		CHECK(time4.asTAIseconds() == 100);
+		CHECK(time4.asCustomCUCTimestamp().elapsed100msTicks == 1003);
+
+		// check rounding errors
+		Time::CustomCUC_t customCUC3 = {1005};
+		TimeStamp<3, 0> time5(customCUC3);
+		CHECK(time5.asTAIseconds() == 100);
+		CHECK(time5.asCustomCUCTimestamp().elapsed100msTicks == 1000);
+		TimeStamp<3, 2> time6(customCUC3);
+		CHECK(time6.asTAIseconds() == 100);
+		CHECK(time6.asCustomCUCTimestamp().elapsed100msTicks == 1005);
+	}
+
+	SECTION("Check idempotence") {
+		Time::CustomCUC_t customCUC1 = {1000};
+		TimeStamp<3, 3> time1(customCUC1);
+		Time::CustomCUC_t customCUC2 = time1.asCustomCUCTimestamp();
+		CHECK(customCUC1.elapsed100msTicks == customCUC2.elapsed100msTicks);
+	}
+}
+
+TEST_CASE("UTC idempotence") {
+	{
+		UTCTimestamp timestamp1(2020, 4, 10, 10, 15, 0); // 10 Apr 2020, 10:15:00;
+		TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(timestamp1);
+		UTCTimestamp timestamp2 = time.toUTCtimestamp();
+		bool cond = (timestamp2 == timestamp1);
+		REQUIRE(cond);
+	}
+	{
+		UTCTimestamp timestamp1(2035, 1, 1, 0, 0, 1); // 1 Jan 2035 midnight passed;
+		TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(timestamp1);
+		UTCTimestamp timestamp2 = time.toUTCtimestamp();
+		bool cond = (timestamp2 == timestamp1);
+		REQUIRE(cond);
+	}
+}
+
+TEST_CASE("UTC conversion to and from seconds timestamps") {
+	{
+		UTCTimestamp timestamp1(2020, 12, 5, 0, 0, 0);
+		TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(timestamp1);
+		REQUIRE(time.asTAIseconds() == 29289600);
+	}
+	{
+		UTCTimestamp timestamp1(2020, 2, 29, 0, 0, 0);
+		TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(timestamp1);
+		REQUIRE(time.asTAIseconds() == 5097600);
+	}
+	{
+		UTCTimestamp timestamp1(2025, 3, 10, 0, 0, 0);
+		TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(timestamp1);
+		REQUIRE(time.asTAIseconds() == 163728000);
+	}
+}
+
+// SECTION("Check different templates, should break at compile"){
+//   TimeStamp<1, 2> time1;
+//   TimeStamp<4, 4> time2;
+//   REQUIRE(time1==time2);
+// }
+
+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
+}
+
+TEST_CASE("Time runtime class size") {
+	int input_time = 1000;
+	TimeStamp<CUCSecondsBytes, CUCFractionalBytes> time(input_time);
+	REQUIRE(sizeof(time) < 32);
+}