/*
 * COPYRIGHT:         See COPYING in the top level directory
 * PROJECT:           ReactOS system libraries
 * FILE:              lib/rtl/time.c
 * PURPOSE:           Conversion between Time and TimeFields
 * PROGRAMMER:        Rex Jolliff (rex@lvcablemodem.com)
 */

/* INCLUDES *****************************************************************/

#include <rtl.h>

#define NDEBUG
#include <debug.h>

#define TICKSPERMIN        600000000
#define TICKSPERSEC        10000000
#define TICKSPERMSEC       10000
#define SECSPERDAY         86400
#define SECSPERHOUR        3600
#define SECSPERMIN         60
#define MINSPERHOUR        60
#define HOURSPERDAY        24
#define EPOCHWEEKDAY       1
#define DAYSPERWEEK        7
#define EPOCHYEAR          1601
#define DAYSPERNORMALYEAR  365
#define DAYSPERLEAPYEAR    366
#define MONSPERYEAR        12

#if defined(__GNUC__)
#define TICKSTO1970         0x019db1ded53e8000LL
#define TICKSTO1980         0x01a8e79fe1d58000LL
#else
#define TICKSTO1970         0x019db1ded53e8000i64
#define TICKSTO1980         0x01a8e79fe1d58000i64
#endif


static const unsigned int YearLengths[2] =
{
    DAYSPERNORMALYEAR, DAYSPERLEAPYEAR
};
static const UCHAR MonthLengths[2][MONSPERYEAR] =
{
    { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
    { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};

static __inline int IsLeapYear(int Year)
{
    return Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0) ? 1 : 0;
}

static int DaysSinceEpoch(int Year)
{
    int Days;
    Year--; /* Don't include a leap day from the current year */
    Days = Year * DAYSPERNORMALYEAR + Year / 4 - Year / 100 + Year / 400;
    Days -= (EPOCHYEAR - 1) * DAYSPERNORMALYEAR + (EPOCHYEAR - 1) / 4 - (EPOCHYEAR - 1) / 100 + (EPOCHYEAR - 1) / 400;
    return Days;
}

/* FUNCTIONS *****************************************************************/

/*
 * @implemented
 */
BOOLEAN NTAPI
RtlCutoverTimeToSystemTime(IN PTIME_FIELDS CutoverTimeFields,
                           OUT PLARGE_INTEGER SystemTime,
                           IN PLARGE_INTEGER CurrentTime,
                           IN BOOLEAN ThisYearsCutoverOnly)
{
    TIME_FIELDS AdjustedTimeFields;
    TIME_FIELDS CurrentTimeFields;
    TIME_FIELDS CutoverSystemTimeFields;
    LARGE_INTEGER CutoverSystemTime;
    UCHAR MonthLength;
    CSHORT Days;
    BOOLEAN NextYearsCutover = FALSE;

    /* Check fixed cutover time */
    if (CutoverTimeFields->Year != 0)
    {
        if (!RtlTimeFieldsToTime(CutoverTimeFields, SystemTime))
            return FALSE;

        if (SystemTime->QuadPart < CurrentTime->QuadPart)
            return FALSE;

        return TRUE;
    }

    /*
     * Compute recurring cutover time
     */

    /* Day must be between 1(first) and 5(last) */
    if (CutoverTimeFields->Day == 0 || CutoverTimeFields->Day > 5)
        return FALSE;

    RtlTimeToTimeFields(CurrentTime, &CurrentTimeFields);

    while (TRUE)
    {
        /* Compute the cutover time of the first day of the current month */
        AdjustedTimeFields.Year = CurrentTimeFields.Year;
        if (NextYearsCutover)
            AdjustedTimeFields.Year++;

        AdjustedTimeFields.Month = CutoverTimeFields->Month;
        AdjustedTimeFields.Day = 1;
        AdjustedTimeFields.Hour = CutoverTimeFields->Hour;
        AdjustedTimeFields.Minute = CutoverTimeFields->Minute;
        AdjustedTimeFields.Second = CutoverTimeFields->Second;
        AdjustedTimeFields.Milliseconds = CutoverTimeFields->Milliseconds;

        if (!RtlTimeFieldsToTime(&AdjustedTimeFields, &CutoverSystemTime))
            return FALSE;

        RtlTimeToTimeFields(&CutoverSystemTime, &CutoverSystemTimeFields);

        /* Adjust day to first matching weekday */
        if (CutoverSystemTimeFields.Weekday != CutoverTimeFields->Weekday)
        {
            if (CutoverSystemTimeFields.Weekday < CutoverTimeFields->Weekday)
                Days = CutoverTimeFields->Weekday - CutoverSystemTimeFields.Weekday;
            else
                Days = DAYSPERWEEK - (CutoverSystemTimeFields.Weekday - CutoverTimeFields->Weekday);

            AdjustedTimeFields.Day += Days;
        }

        /* Adjust the number of weeks */
        if (CutoverTimeFields->Day > 1)
        {
            Days = DAYSPERWEEK * (CutoverTimeFields->Day - 1);
            MonthLength = MonthLengths[IsLeapYear(AdjustedTimeFields.Year)][AdjustedTimeFields.Month - 1];
            if ((AdjustedTimeFields.Day + Days) > MonthLength)
                Days -= DAYSPERWEEK;

            AdjustedTimeFields.Day += Days;
        }

        if (!RtlTimeFieldsToTime(&AdjustedTimeFields, &CutoverSystemTime))
            return FALSE;

        if (ThisYearsCutoverOnly ||
            NextYearsCutover ||
            CutoverSystemTime.QuadPart >= CurrentTime->QuadPart)
        {
            break;
        }

        NextYearsCutover = TRUE;
    }

    SystemTime->QuadPart = CutoverSystemTime.QuadPart;

    return TRUE;
}


/*
 * @implemented
 */
BOOLEAN
NTAPI
RtlTimeFieldsToTime(IN PTIME_FIELDS TimeFields,
                    OUT PLARGE_INTEGER Time)
{
    ULONG CurMonth;
    TIME_FIELDS IntTimeFields;

    RtlCopyMemory(&IntTimeFields,
                  TimeFields,
                  sizeof(TIME_FIELDS));

    if (TimeFields->Milliseconds < 0 || TimeFields->Milliseconds > 999 ||
        TimeFields->Second < 0 || TimeFields->Second > 59 ||
        TimeFields->Minute < 0 || TimeFields->Minute > 59 ||
        TimeFields->Hour < 0 || TimeFields->Hour > 23 ||
        TimeFields->Month < 1 || TimeFields->Month > 12 ||
        TimeFields->Day < 1 ||
        TimeFields->Day >
            MonthLengths[IsLeapYear(TimeFields->Year)][TimeFields->Month - 1] ||
        TimeFields->Year < 1601)
    {
        return FALSE;
    }

    /* Compute the time */
    Time->QuadPart = DaysSinceEpoch(IntTimeFields.Year);
    for (CurMonth = 1; CurMonth < IntTimeFields.Month; CurMonth++)
    {
        Time->QuadPart += MonthLengths[IsLeapYear(IntTimeFields.Year)][CurMonth - 1];
    }
    Time->QuadPart += IntTimeFields.Day - 1;
    Time->QuadPart *= SECSPERDAY;
    Time->QuadPart += IntTimeFields.Hour * SECSPERHOUR + IntTimeFields.Minute * SECSPERMIN +
                      IntTimeFields.Second;
    Time->QuadPart *= TICKSPERSEC;
    Time->QuadPart += IntTimeFields.Milliseconds * TICKSPERMSEC;

    return TRUE;
}


/*
 * @implemented
 */
VOID
NTAPI
RtlTimeToElapsedTimeFields(IN PLARGE_INTEGER Time,
                           OUT PTIME_FIELDS TimeFields)
{
    ULONGLONG ElapsedSeconds;
    ULONG SecondsInDay;
    ULONG SecondsInMinute;

    /* Extract millisecond from time */
    TimeFields->Milliseconds = (CSHORT)((Time->QuadPart % TICKSPERSEC) / TICKSPERMSEC);

    /* Compute elapsed seconds */
    ElapsedSeconds = (ULONGLONG)Time->QuadPart / TICKSPERSEC;

    /* Compute seconds within the day */
    SecondsInDay = ElapsedSeconds % SECSPERDAY;

    /* Compute elapsed minutes within the day */
    SecondsInMinute = SecondsInDay % SECSPERHOUR;

    /* Compute elapsed time of day */
    TimeFields->Hour = (CSHORT)(SecondsInDay / SECSPERHOUR);
    TimeFields->Minute = (CSHORT)(SecondsInMinute / SECSPERMIN);
    TimeFields->Second = (CSHORT)(SecondsInMinute % SECSPERMIN);

    /* Compute elapsed days */
    TimeFields->Day = (CSHORT)(ElapsedSeconds / SECSPERDAY);

    /* The elapsed number of months and days cannot be calculated */
    TimeFields->Month = 0;
    TimeFields->Year = 0;
}


/*
 * @implemented
 */
VOID
NTAPI
RtlTimeToTimeFields(IN PLARGE_INTEGER Time,
                    OUT PTIME_FIELDS TimeFields)
{
    const UCHAR *Months;
    ULONG SecondsInDay, CurYear;
    ULONG LeapYear, CurMonth;
    ULONG Days;
    ULONGLONG IntTime = Time->QuadPart;

    /* Extract millisecond from time and convert time into seconds */
    TimeFields->Milliseconds = (CSHORT)((IntTime % TICKSPERSEC) / TICKSPERMSEC);
    IntTime = IntTime / TICKSPERSEC;

    /* Split the time into days and seconds within the day */
    Days = (ULONG)(IntTime / SECSPERDAY);
    SecondsInDay = IntTime % SECSPERDAY;

    /* Compute time of day */
    TimeFields->Hour = (CSHORT)(SecondsInDay / SECSPERHOUR);
    SecondsInDay = SecondsInDay % SECSPERHOUR;
    TimeFields->Minute = (CSHORT)(SecondsInDay / SECSPERMIN);
    TimeFields->Second = (CSHORT)(SecondsInDay % SECSPERMIN);

    /* Compute day of week */
    TimeFields->Weekday = (CSHORT)((EPOCHWEEKDAY + Days) % DAYSPERWEEK);

    /* Compute year */
    CurYear = EPOCHYEAR;
    CurYear += Days / DAYSPERLEAPYEAR;
    Days -= DaysSinceEpoch(CurYear);
    while (TRUE)
    {
        LeapYear = IsLeapYear(CurYear);
        if (Days < YearLengths[LeapYear])
        {
            break;
        }
        CurYear++;
        Days = Days - YearLengths[LeapYear];
    }
    TimeFields->Year = (CSHORT)CurYear;

    /* Compute month of year */
    LeapYear = IsLeapYear(CurYear);
    Months = MonthLengths[LeapYear];
    for (CurMonth = 0; Days >= Months[CurMonth]; CurMonth++)
    {
        Days = Days - Months[CurMonth];
    }
    TimeFields->Month = (CSHORT)(CurMonth + 1);
    TimeFields->Day = (CSHORT)(Days + 1);
}


/*
 * @implemented
 */
BOOLEAN
NTAPI
RtlTimeToSecondsSince1970(IN PLARGE_INTEGER Time,
                          OUT PULONG SecondsSince1970)
{
    LARGE_INTEGER IntTime;

    IntTime.QuadPart = Time->QuadPart - TICKSTO1970;
    IntTime.QuadPart = IntTime.QuadPart / TICKSPERSEC;

    if (IntTime.u.HighPart != 0)
        return FALSE;

    *SecondsSince1970 = IntTime.u.LowPart;

    return TRUE;
}


/*
 * @implemented
 */
BOOLEAN
NTAPI
RtlTimeToSecondsSince1980(IN PLARGE_INTEGER Time,
                          OUT PULONG SecondsSince1980)
{
    LARGE_INTEGER IntTime;

    IntTime.QuadPart = Time->QuadPart - TICKSTO1980;
    IntTime.QuadPart = IntTime.QuadPart / TICKSPERSEC;

    if (IntTime.u.HighPart != 0)
        return FALSE;

    *SecondsSince1980 = IntTime.u.LowPart;

    return TRUE;
}


/*
 * @implemented
 */
NTSTATUS
NTAPI
RtlLocalTimeToSystemTime(IN PLARGE_INTEGER LocalTime,
                         OUT PLARGE_INTEGER SystemTime)
{
    SYSTEM_TIMEOFDAY_INFORMATION TimeInformation;
    NTSTATUS Status;

    Status = ZwQuerySystemInformation(SystemTimeOfDayInformation,
                                      &TimeInformation,
                                      sizeof(TimeInformation),
                                      NULL);
    if (!NT_SUCCESS(Status))
        return Status;

    SystemTime->QuadPart = LocalTime->QuadPart +
                           TimeInformation.TimeZoneBias.QuadPart;

    return STATUS_SUCCESS;
}


/*
 * @implemented
 */
NTSTATUS
NTAPI
RtlSystemTimeToLocalTime(IN PLARGE_INTEGER SystemTime,
                         OUT PLARGE_INTEGER LocalTime)
{
    SYSTEM_TIMEOFDAY_INFORMATION TimeInformation;
    NTSTATUS Status;

    Status = ZwQuerySystemInformation(SystemTimeOfDayInformation,
                                      &TimeInformation,
                                      sizeof(TimeInformation),
                                      NULL);
    if (!NT_SUCCESS(Status))
        return Status;

    LocalTime->QuadPart = SystemTime->QuadPart -
                          TimeInformation.TimeZoneBias.QuadPart;

    return STATUS_SUCCESS;
}


/*
 * @implemented
 */
VOID
NTAPI
RtlSecondsSince1970ToTime(IN ULONG SecondsSince1970,
                          OUT PLARGE_INTEGER Time)
{
    Time->QuadPart = ((LONGLONG)SecondsSince1970 * TICKSPERSEC) + TICKSTO1970;
}


/*
 * @implemented
 */
VOID NTAPI
RtlSecondsSince1980ToTime(IN ULONG SecondsSince1980,
                          OUT PLARGE_INTEGER Time)
{
    Time->QuadPart = ((LONGLONG)SecondsSince1980 * TICKSPERSEC) + TICKSTO1980;
}

/* EOF */