#include "pch.h"
#include "System/DateTime.h"
#include "System/ArgumentOutOfRangeException.h"
#include "System/ArgumentException.h"
#include "System/FormatException.h"
#include "System/String.h"
#include <chrono>
#include <time.h>
#include <cmath>
#include <iomanip>
#include <sstream>

namespace DotNetDupe {
	namespace System {

		constexpr int64_t TicksPerMillisecond = 10000;
		constexpr int64_t TicksPerSecond = TicksPerMillisecond * 1000;
		constexpr int64_t TicksPerMinute = TicksPerSecond * 60;
		constexpr int64_t TicksPerHour = TicksPerMinute * 60;
		constexpr int64_t TicksPerDay = TicksPerHour * 24;
		constexpr int64_t MinTicks = 0;
		constexpr int64_t MaxTicks = 3155378975999999999LL;
		constexpr int64_t UnixEpochTicks = 621355968000000000LL;

		constexpr int DaysToMonth365 [] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
		constexpr int DaysToMonth366 [] = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 };

		static int64_t DateToTicks(int year, int month, int day) {
			if (year >= 1 && year <= 9999 && month >= 1 && month <= 12) {
				const int* days = DateTime::IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365;
				if (day >= 1 && day <= days [month] - days [month - 1]) {
					int y = year - 1;
					int n = y * 365 + y / 4 - y / 100 + y / 400 + days [month - 1] + day - 1;
					return (int64_t)n * TicksPerDay;
				}
			}
			throw ArgumentOutOfRangeException("Year, Month, and Day parameters describe an un-representable DateTime.");
		}

		static int64_t TimeToTicks(int hour, int minute, int second) {
			if (hour >= 0 && hour < 24 && minute >= 0 && minute < 60 && second >= 0 && second < 60) {
				int64_t totalSeconds = (int64_t)hour * 3600 + (int64_t)minute * 60 + (int64_t)second;
				return totalSeconds * TicksPerSecond;
			}
			throw ArgumentOutOfRangeException("Hour, Minute, and Second parameters describe an un-representable DateTime.");
		}

		static void GetDatePart(int64_t ticks, int& year, int& month, int& day) {
			int n = (int)(ticks / TicksPerDay);
			int y400 = n / 146097; n -= y400 * 146097;
			int y100 = n / 36524; if (y100 == 4) y100 = 3; n -= y100 * 36524;
			int y4 = n / 1461; n -= y4 * 1461;
			int y1 = n / 365; if (y1 == 4) y1 = 3; n -= y1 * 365;
			year = y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1;
			const int* days = DateTime::IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365;
			month = 1;
			while (n >= days [month]) month++;
			day = n - days [month - 1] + 1;
		}

		DateTime::DateTime(int year, int month, int day)
			: m_nTicks(DateToTicks(year, month, day)), m_kind(DateTimeKind::Unspecified) { }

		DateTime::DateTime(int year, int month, int day, int hour, int minute, int second)
			: m_nTicks(DateToTicks(year, month, day) + TimeToTicks(hour, minute, second)), m_kind(DateTimeKind::Unspecified) { }

		DateTime::DateTime(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind)
			: m_nTicks(DateToTicks(year, month, day) + TimeToTicks(hour, minute, second)), m_kind(kind) { }

		DateTime::DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond)
			: m_nTicks(DateToTicks(year, month, day) + TimeToTicks(hour, minute, second) + millisecond * TicksPerMillisecond), m_kind(DateTimeKind::Unspecified) {
			if (millisecond < 0 || millisecond >= 1000)
				throw ArgumentOutOfRangeException("Millisecond must be between 0 and 999.");
		}

		DateTime::DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, DateTimeKind kind)
			: m_nTicks(DateToTicks(year, month, day) + TimeToTicks(hour, minute, second) + millisecond * TicksPerMillisecond), m_kind(kind) {
			if (millisecond < 0 || millisecond >= 1000)
				throw ArgumentOutOfRangeException("Millisecond must be between 0 and 999.");
		}

		int DateTime::GetYear() const { int y, m, d; GetDatePart(m_nTicks, y, m, d); return y; }
		int DateTime::GetMonth() const { int y, m, d; GetDatePart(m_nTicks, y, m, d); return m; }
		int DateTime::GetDay() const { int y, m, d; GetDatePart(m_nTicks, y, m, d); return d; }
		int DateTime::GetHour() const { return (int)((m_nTicks / TicksPerHour) % 24); }
		int DateTime::GetMinute() const { return (int)((m_nTicks / TicksPerMinute) % 60); }
		int DateTime::GetSecond() const { return (int)((m_nTicks / TicksPerSecond) % 60); }
		int DateTime::GetMillisecond() const { return (int)((m_nTicks / TicksPerMillisecond) % 1000); }

		int DateTime::GetDayOfYear() const {
			int y, m, d;
			GetDatePart(m_nTicks, y, m, d);
			const int* days = IsLeapYear(y) ? DaysToMonth366 : DaysToMonth365;
			return days [m - 1] + d;
		}

		int DateTime::GetDayOfWeek() const {
			return (int)((m_nTicks / TicksPerDay + 1) % 7);
		}

		DateTime DateTime::GetDate() const {
			return DateTime(m_nTicks - (m_nTicks % TicksPerDay), m_kind);
		}

		TimeSpan DateTime::GetTimeOfDay() const {
			return TimeSpan(m_nTicks % TicksPerDay);
		}

		DateTime DateTime::Add(TimeSpan value) const {
			return AddTicks(value.GetTicks());
		}

		DateTime DateTime::AddDays(double value) const {
			return AddMilliseconds(value * 86400000.0);
		}

		DateTime DateTime::AddHours(double value) const {
			return AddMilliseconds(value * 3600000.0);
		}

		DateTime DateTime::AddMilliseconds(double value) const {
			int64_t ticks = (int64_t)(value * TicksPerMillisecond + (value >= 0.0 ? 0.5 : -0.5));
			return AddTicks(ticks);
		}

		DateTime DateTime::AddMinutes(double value) const {
			return AddMilliseconds(value * 60000.0);
		}

		DateTime DateTime::AddMonths(int months) const {
			if (months < -120000 || months > 120000) throw ArgumentOutOfRangeException("Months value is out of range.");
			int y, m, d; GetDatePart(m_nTicks, y, m, d);
			int i = m - 1 + months;
			if (i >= 0) { m = i % 12 + 1; y = y + i / 12; }
			else { m = 12 + (i + 1) % 12; y = y + (i - 11) / 12; }
			if (y < 1 || y > 9999) throw ArgumentOutOfRangeException("DateTime result is out of range.");
			int days = DaysInMonth(y, m);
			if (d > days) d = days;
			return DateTime((int64_t)(DateToTicks(y, m, d) + m_nTicks % TicksPerDay), m_kind);
		}

		DateTime DateTime::AddSeconds(double value) const {
			return AddMilliseconds(value * 1000.0);
		}

		DateTime DateTime::AddTicks(int64_t value) const {
			int64_t ticks = m_nTicks + value;
			if (ticks < MinTicks || ticks > MaxTicks) throw ArgumentOutOfRangeException("DateTime result is out of range.");
			return DateTime(ticks, m_kind);
		}

		DateTime DateTime::AddYears(int value) const {
			if (value < -10000 || value > 10000) throw ArgumentOutOfRangeException("Years value is out of range.");
			return AddMonths(value * 12);
		}

		DateTime DateTime::Now() {
			DateTime utc = UtcNow();
			return utc.ToLocalTime();
		}

		DateTime DateTime::UtcNow() {
			auto now = std::chrono::system_clock::now();
			auto duration = now.time_since_epoch();
			int64_t micro = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
			int64_t ticks = (micro * 10) + UnixEpochTicks;
			return DateTime(ticks, DateTimeKind::Utc);
		}

		DateTime DateTime::Today() {
			return Now().GetDate();
		}

        static int64_t TmToTicks(const struct tm& t, int64_t remainderTicks) {
            int year = t.tm_year + 1900, month = t.tm_mon + 1, day = t.tm_mday;
            int hour = t.tm_hour, minute = t.tm_min, second = t.tm_sec;
            return DateToTicks(year, month, day) + TimeToTicks(hour, minute, second) + remainderTicks;
        }

        static struct tm ConvertToLocalTm(int64_t time) {
            struct tm t;
#if defined(_WIN32)
            _localtime64_s(&t, &time);
#else
            time_t time_t_val = static_cast<time_t>(time);
            localtime_r(&time_t_val, &t);
#endif
            return t;
        }

        static struct tm ConvertToUtcTm(int64_t time) {
            struct tm t;
#if defined(_WIN32)
            _gmtime64_s(&t, &time);
#else
            time_t time_t_val = static_cast<time_t>(time);
            gmtime_r(&time_t_val, &t);
#endif
            return t;
        }

		DateTime DateTime::ToLocalTime() const {
			if (m_kind == DateTimeKind::Local) return *this;
			int64_t time = (m_nTicks - UnixEpochTicks) / TicksPerSecond;
			struct tm t = ConvertToLocalTm(time);
			return DateTime(TmToTicks(t, m_nTicks % TicksPerSecond), DateTimeKind::Local);
		}

		DateTime DateTime::ToUniversalTime() const {
			if (m_kind == DateTimeKind::Utc) return *this;
			int64_t time = (m_nTicks - UnixEpochTicks) / TicksPerSecond;
			struct tm t = ConvertToUtcTm(time);
			return DateTime(TmToTicks(t, m_nTicks % TicksPerSecond), DateTimeKind::Utc);
		}

		String DateTime::ToString() const {
			return ToString("yyyy-MM-dd HH:mm:ss");
		}

		String DateTime::ToString(const String& sFormat) const {
			std::stringstream ss;
			int y, m, d; GetDatePart(m_nTicks, y, m, d);
			ss << std::setfill('0') << std::setw(4) << y << "-"
				<< std::setw(2) << m << "-" << std::setw(2) << d << " "
				<< std::setw(2) << GetHour() << ":" << std::setw(2) << GetMinute() << ":" << std::setw(2) << GetSecond();
			return String(ss.str().c_str());
		}

		int DateTime::DaysInMonth(int year, int month) {
			if (month < 1 || month > 12) throw ArgumentOutOfRangeException("Month must be between 1 and 12.");
			const int* days = IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365;
			return days [month] - days [month - 1];
		}

		bool DateTime::IsLeapYear(int year) {
			if (year < 1 || year > 9999) throw ArgumentOutOfRangeException("Year must be between 1 and 9999.");
			return (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0);
		}

		static std::string ConvertFormat(const String& format) {
			std::string f = format.GetRawString();
			size_t pos = 0;
			while ((pos = f.find("yyyy")) != std::string::npos) f.replace(pos, 4, "%Y");
			while ((pos = f.find("MM")) != std::string::npos) f.replace(pos, 2, "%m");
			while ((pos = f.find("dd")) != std::string::npos) f.replace(pos, 2, "%d");
			while ((pos = f.find("HH")) != std::string::npos) f.replace(pos, 2, "%H");
			while ((pos = f.find("mm")) != std::string::npos) f.replace(pos, 2, "%M");
			while ((pos = f.find("ss")) != std::string::npos) f.replace(pos, 2, "%S");
			return f;
		}

		static bool ParseDateTimeStr(const String& s, const String& format, DateTime& result) {
			std::istringstream ss(s.GetRawString());
			std::tm t = {};
			std::string f = ConvertFormat(format);
			ss >> std::get_time(&t, f.c_str());
			if (ss.fail()) return false;
			try {
				result = DateTime(t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
				return true;
			}
			catch (const Exception&) { return false; }
			catch (const std::exception&) { return false; }
		}

		bool DateTime::TryParseExact(const String& s, const String& format, DateTime& result) {
			return ParseDateTimeStr(s, format, result);
		}

		bool DateTime::TryParse(const String& s, DateTime& result) {
			return ParseDateTimeStr(s, "yyyy-MM-dd HH:mm:ss", result);
		}

		DateTime DateTime::ParseExact(const String& s, const String& format) {
			DateTime result(1, 1, 1);
			if (!TryParseExact(s, format, result))
				throw FormatException("String was not recognized as a valid DateTime.");
			return result;
		}

		DateTime DateTime::Parse(const String& s) {
			DateTime result(1, 1, 1);
			if (!TryParse(s, result))
				throw FormatException("String was not recognized as a valid DateTime.");
			return result;
		}

	} // namespace System
} // namespace DotNetDupe

Generated by OpenCppCoverage (Version: 0.9.9.0)