diff --git a/xtoskrnl/includes/rtl/time.hh b/xtoskrnl/includes/rtl/time.hh index c28c042..a685869 100644 --- a/xtoskrnl/includes/rtl/time.hh +++ b/xtoskrnl/includes/rtl/time.hh @@ -25,6 +25,10 @@ namespace RTL OUT PLONGLONG UnixTime); STATIC XTAPI XTSTATUS TimeFieldsToXtEpoch(IN PTIME_FIELDS TimeFields, OUT PLARGE_INTEGER XtTime); + STATIC XTAPI XTSTATUS UnixEpochToTimeFields(IN PLONGLONG UnixTime, + OUT PTIME_FIELDS TimeFields); + STATIC XTAPI XTSTATUS XtEpochToTimeFields(IN PLARGE_INTEGER XtTime, + OUT PTIME_FIELDS TimeFields); private: STATIC XTFASTCALL BOOLEAN LeapYear(SHORT Year); diff --git a/xtoskrnl/rtl/time.cc b/xtoskrnl/rtl/time.cc index 350aeee..5d92f97 100644 --- a/xtoskrnl/rtl/time.cc +++ b/xtoskrnl/rtl/time.cc @@ -161,3 +161,223 @@ RTL::Time::TimeFieldsToXtEpoch(IN PTIME_FIELDS TimeFields, /* Return success */ return STATUS_SUCCESS; } + +/** + * Converts a 64-bit Unix timestamp into a TIME_FIELDS calendar structure. + * + * @param UnixTime + * Supplies a pointer to a 64-bit integer that contains the number of + * seconds elapsed since January 1, 1970. + * + * @param TimeFields + * Supplies a pointer to a TIME_FIELDS structure that receives the converted calendar data. + * + * @return This routine returns a status code. + * + * @since XT 1.0 + */ +XTAPI +XTSTATUS +RTL::Time::UnixEpochToTimeFields(IN PLONGLONG UnixTime, + OUT PTIME_FIELDS TimeFields) +{ + LONG CalculatedDay, CalculatedMonth, CalculatedYear; + LONGLONG Era, SecondsOfDay, TotalSeconds, UnixDays; + ULONG DayOfEra, DayOfYear, YearOfEra; + + /* Validate pointers */ + if(UnixTime == NULLPTR || TimeFields == NULLPTR) + { + /* Invalid pointers provided, return error code */ + return STATUS_INVALID_PARAMETER; + } + + /* Extract total seconds */ + TotalSeconds = *UnixTime; + + /* Check sign */ + if(TotalSeconds >= 0) + { + /* Divide TotalSeconds by seconds per day and compute the remainder */ + UnixDays = TotalSeconds / TIME_SECONDS_PER_DAY; + SecondsOfDay = TotalSeconds % TIME_SECONDS_PER_DAY; + } + else + { + /* Subtract seconds per day minus one from TotalSeconds and divide by seconds per day */ + UnixDays = (TotalSeconds - (TIME_SECONDS_PER_DAY - 1)) / TIME_SECONDS_PER_DAY; + SecondsOfDay = TotalSeconds % TIME_SECONDS_PER_DAY; + + /* Check if SecondsOfDay is less than zero */ + if(SecondsOfDay < 0) + { + /* Add seconds per day to SecondsOfDay */ + SecondsOfDay += TIME_SECONDS_PER_DAY; + } + } + + /* Unix timestamps do not natively support milliseconds */ + TimeFields->Milliseconds = 0; + + /* Shift epoch from 1970-01-01 to 0000-03-01 to align with leap year cycles */ + UnixDays += 719468LL; + + /* Calculate the 400-year era */ + Era = (UnixDays >= 0 ? UnixDays : UnixDays - 146096) / 146097; + + /* Calculate the day within the current era */ + DayOfEra = (ULONG)(UnixDays - Era * 146097); + + /* Calculate the year within the current era */ + YearOfEra = (DayOfEra - (DayOfEra / 1460) + (DayOfEra / 36524) - (DayOfEra / 146096)) / 365; + + /* Combine era and year-of-era to get the absolute year */ + CalculatedYear = (LONG)(YearOfEra + Era * 400); + + /* Calculate the day of the year */ + DayOfYear = DayOfEra - (365 * YearOfEra + (YearOfEra / 4) - (YearOfEra / 100)); + + /* Calculate the month */ + CalculatedMonth = (5 * DayOfYear + 2) / 153; + + /* Calculate the exact day of the month */ + CalculatedDay = DayOfYear - (153 * CalculatedMonth + 2) / 5 + 1; + + /* Shift the month back to the standard Gregorian calendar */ + CalculatedMonth += (CalculatedMonth < 10 ? 3 : -9); + + /* If the month is January or February, it belongs to the next computational year */ + CalculatedYear += (CalculatedMonth <= 2 ? 1 : 0); + + /* Validate computed year */ + if(CalculatedYear < 0 || CalculatedYear > 30827) + { + /* Year is out of bounds, return error code */ + return STATUS_NOT_SUPPORTED; + } + + /* Populate the output structure */ + TimeFields->Day = (SHORT)CalculatedDay; + TimeFields->Month = (SHORT)CalculatedMonth; + TimeFields->Year = (SHORT)CalculatedYear; + TimeFields->Hour = (SHORT)(SecondsOfDay / TIME_SECONDS_PER_HOUR); + TimeFields->Minute = (SHORT)((SecondsOfDay % TIME_SECONDS_PER_HOUR) / TIME_SECONDS_PER_MINUTE); + TimeFields->Second = (SHORT)(SecondsOfDay % TIME_SECONDS_PER_MINUTE); + TimeFields->Milliseconds = 0; + TimeFields->Weekday = (SHORT)(((UnixDays + 4) % 7 + 7) % 7); + + /* Return success */ + return STATUS_SUCCESS; +} + +/** + * Converts a 64-bit XT timestamp into a TIME_FIELDS calendar structure. + * + * @param XtTime + * Supplies a pointer to a variable that contains the time value in + * 100-nanosecond intervals since January 1, 1601. + * + * @param TimeFields + * Supplies a pointer to a TIME_FIELDS structure that receives the converted calendar data. + * + * @return This routine returns a status code. + * + * @since XT 1.0 + */ +XTAPI +XTSTATUS +RTL::Time::XtEpochToTimeFields(IN PLARGE_INTEGER XtTime, + OUT PTIME_FIELDS TimeFields) +{ + ULONG CurrentYear, Days, Leap, Periods1Year, Periods4Years, Periods100Years, Periods400Years; + ULONGLONG TotalSeconds, ElapsedDays, TotalSecondsOfDay; + + /* Validate pointers */ + if(XtTime == NULLPTR || TimeFields == NULLPTR) + { + /* Invalid pointers provided, return error code */ + return STATUS_INVALID_PARAMETER; + } + + /* The XT Epoch starts at 1601, negative absolute system time is not supported */ + if(XtTime->QuadPart < 0) + { + /* Invalid time value, return error code */ + return STATUS_INVALID_PARAMETER; + } + + /* Extract ticks into whole seconds and remaining milliseconds */ + TotalSeconds = XtTime->QuadPart / TIME_TICKS_PER_SECOND; + TimeFields->Milliseconds = (SHORT)((XtTime->QuadPart % TIME_TICKS_PER_SECOND) / TIME_TICKS_PER_MILLISECOND); + + /* Split total seconds into absolute elapsed days and time of the current day */ + ElapsedDays = TotalSeconds / TIME_SECONDS_PER_DAY; + TotalSecondsOfDay = TotalSeconds % TIME_SECONDS_PER_DAY; + + /* Calculate hour, minute, and second */ + TimeFields->Hour = (SHORT)(TotalSecondsOfDay / TIME_SECONDS_PER_HOUR); + TimeFields->Minute = (SHORT)((TotalSecondsOfDay % TIME_SECONDS_PER_HOUR) / TIME_SECONDS_PER_MINUTE); + TimeFields->Second = (SHORT)(TotalSecondsOfDay % TIME_SECONDS_PER_MINUTE); + + /* Calculate weekday */ + TimeFields->Weekday = (SHORT)((ElapsedDays + 1) % 7); + + /* Calculate the year using the Gregorian leap year cycles */ + Days = (ULONG)ElapsedDays; + CurrentYear = 1601; + + /* Calculate completed 400-year periods */ + Periods400Years = Days / 146097; + CurrentYear += Periods400Years * 400; + Days %= 146097; + + /* Calculate completed 100-year periods */ + Periods100Years = Days / 36524; + if(Periods100Years == 4) + { + /* The leap year at the very end of a 400-year cycle */ + Periods100Years = 3; + } + + /* Update the current year and remaining days */ + CurrentYear += Periods100Years * 100; + Days -= Periods100Years * 36524; + + /* Calculate completed 4-year periods */ + Periods4Years = Days / 1461; + CurrentYear += Periods4Years * 4; + Days %= 1461; + + /* Calculate completed 1-year periods */ + Periods1Year = Days / 365; + if(Periods1Year == 4) + { + /* The leap year at the end of a normal 4-year cycle */ + Periods1Year = 3; + } + + /* Update the current year and remaining days */ + CurrentYear += Periods1Year; + Days -= Periods1Year * 365; + + /* Set the calculated year */ + TimeFields->Year = (SHORT)CurrentYear; + + /* Check if the calculated year is a leap year */ + Leap = LeapYear(TimeFields->Year) ? 1 : 0; + TimeFields->Month = 1; + + /* Subtract days of each month to find the current month */ + while(Days >= DaysInMonth[Leap][TimeFields->Month - 1]) + { + /* Calculate the days of the current month and advance the month index */ + Days -= DaysInMonth[Leap][TimeFields->Month - 1]; + TimeFields->Month++; + } + + /* Store the remainder as the 1-based day of the month */ + TimeFields->Day = (SHORT)(Days + 1); + + /* Return success */ + return STATUS_SUCCESS; +}