//See ::/Doc/TimeDate.DD

U16 month_start_days[12]                = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
U16 month_start_days_leap[12]   = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335};

I64 YearStartDate(I64 year)
{//32-bit day since AD 0, given year number.
        I64 y1 = year - 1, yd4000 = y1 / 4000, yd400 = y1 / 400, yd100 = y1 / 100, yd4 = y1 / 4;

        return year * 365 + yd4 - yd100 + yd400 - yd4000;
}

CDate Struct2Date(CDateStruct *_ds)
{//Convert CDateStruct to CDate.
        CDate cdt;
        I64   i1, i2;

        i1 = YearStartDate(_ds->year);
        i2 = YearStartDate(_ds->year + 1);
        if (i2 - i1 == 365)
                i1 += month_start_days[_ds->mon - 1];
        else
                i1 += month_start_days_leap[_ds->mon - 1];
        cdt.date = i1 + _ds->day_of_mon - 1;
        cdt.time = (_ds->sec10000 + 100 * (_ds->sec100 + 100 * (_ds->sec + 60 * (_ds->min + 60 * _ds->hour))))
                                << 21 / (15 * 15 * 3 * 625);
        return cdt;
}

I64 DayOfWeek(I64 i)
{//Day of week, given 32-bit day since AD 0.
        i += CDATE_BASE_DAY_OF_WEEK;
        if (i >= 0)
                return i % 7;
        else
                return 6 - (6 - i) % 7;
}

U0 Date2Struct(CDateStruct *_ds, CDate cdt)
{//Convert CDate to CDateStruct.
        I64 i, k, date = cdt.date;

        _ds->day_of_week = DayOfWeek(date);
        _ds->year = (date + 1) * 100000 / CDATE_YEAR_DAYS_INT;
        i = YearStartDate(_ds->year);

        while (i > date)
        {
                _ds->year--;
                i = YearStartDate(_ds->year);
        }
        date -= i;
        if (YearStartDate(_ds->year + 1) - i == 365)
        {
                k = 0;
                while (date >= month_start_days[k + 1] && k < 11)
                        k++;
                date -= month_start_days[k];
        }
        else
        {
                k = 0;
                while (date >= month_start_days_leap[k + 1] && k < 11)
                        k++;
                date -= month_start_days_leap[k];
        }
        _ds->mon = k + 1;
        _ds->day_of_mon = date + 1;
        k = (625 * 15 * 15 * 3 * cdt.time) >> 21 + 1;
        _ds->sec10000   = ModU64(&k, 100);
        _ds->sec100             = ModU64(&k, 100);
        _ds->sec                = ModU64(&k, 60);
        _ds->min                = ModU64(&k, 60);
        _ds->hour               = k;
}

I64 FirstDayOfMon(I64 i)
{//First day of month, given 32-bit day since AD 0.
        CDateStruct     ds;
        CDate           cdt = 0;

        cdt.date = i;
        Date2Struct(&ds, cdt);
        ds.day_of_mon = 1;
        cdt = Struct2Date(&ds);

        return cdt.date;
}

I64 LastDayOfMon(I64 i)
{//Last day of month, given 32-bit day since AD 0.
        CDateStruct     ds;
        CDate           cdt = 0;

        cdt.date = i;
        Date2Struct(&ds, cdt);
        ds.mon++;
        if (ds.mon == 13)
        {
                ds.mon = 0;
                ds.year++;
        }
        ds.day_of_mon = 1;
        cdt = Struct2Date(&ds);

        return cdt.date - 1;
}

I64 FirstDayOfYear(I64 i)
{//First day of year, given 32-bit day since AD 0.
        CDateStruct     ds;
        CDate           cdt = 0;

        cdt.date = i;
        Date2Struct(&ds, cdt);
        ds.day_of_mon = 1;
        ds.mon = 1;
        cdt = Struct2Date(&ds);

        return cdt.date;
}

I64 LastDayOfYear(I64 i)
{//Last day of year, given 32-bit day since AD 0.
        CDateStruct     ds;
        CDate           cdt = 0;

        cdt.date = i;
        Date2Struct(&ds, cdt);
        ds.day_of_mon = 1;
        ds.mon = 1;
        ds.year++;
        cdt = Struct2Date(&ds);

        return cdt.date - 1;
}

U8 CMOSRegRead(I64 register)
{//Read val from CMOS register. See CMOS Registers.
        OutU8(CMOS_SEL, register);

        return InU8(CMOS_DATA);
}

U0 CMOSRegWrite(I64 register, I64 val)
{//Write val to CMOS register. See CMOS Registers.
        OutU8(CMOS_SEL, register);
        OutU8(CMOS_DATA, val);
}

Bool CMOSIsBcd()
{//Check of CMOS is in binary-coded decimal mode.
// Ex: 15:32:44 == 0x15:0x32:0x44 (not good). We use Bcd2Binary() to convert.
        return !(CMOSRegRead(CMOSR_STATUS_B) & CMOSF_BINARY);
}

I64 Bcd2Binary(U64 b)
{
        I64 i, res = 0;

        for (i = 0; i < 16; i++)
        {
                res = res * 10 + b >> 60;
                b <<= 4;
        }

        return res;
}

U0 NowDateTimeStruct(CDateStruct *_ds)
{
        I64 i;
        U8 *b = _ds;

        MemSet(_ds, 0, sizeof(CDateStruct));
        PUSHFD
        CLI
        while (LBts(&sys_semas[SEMA_SYS_DATE], 0))
                PAUSE

        do
        {
                while (CMOSRegRead(CMOSR_STATUS_A) & CMOSF_UPDATING)
                        PAUSE

                b[2] = CMOSRegRead(CMOSR_SEC);
                b[3] = CMOSRegRead(CMOSR_MIN);
                b[4] = CMOSRegRead(CMOSR_HOUR);
                b[5] = CMOSRegRead(CMOSR_DAY_OF_WEEK);
                b[6] = CMOSRegRead(CMOSR_DAY_OF_MONTH);
                b[7] = CMOSRegRead(CMOSR_MONTH);
                b[8] = CMOSRegRead(CMOSR_YEAR);

        }
        while (CMOSRegRead(CMOSR_STATUS_A) & CMOSF_UPDATING);

        LBtr(&sys_semas[SEMA_SYS_DATE], 0);
        POPFD
        if (CMOSIsBcd)
                for (i = 2; i < 9; i++)
                        b[i] = Bcd2Binary(b[i]);

        if (_ds->year > 255)            _ds->year = 255;
        _ds->year += 2000;

        if (_ds->mon > 12)                      _ds->mon                        = 12;
        if (_ds->day_of_mon > 31)       _ds->day_of_mon         = 31;
        if (_ds->day_of_week > 6)       _ds->day_of_week        = 6;
        if (_ds->hour > 23)             _ds->hour                       = 23;
        if (_ds->min > 59)                      _ds->min                        = 59;
        if (_ds->sec > 59)                      _ds->sec                        = 59;
}

CDate Now()
{//Current datetime.
        CDateStruct ds;

        NowDateTimeStruct(&ds);
        return Struct2Date(&ds)-local_time_offset;
}

U0 TimeSet(CDateStruct *ds)
{//Set CMOS time from user crafted CDateStruct.
//Make sure to use hex as decimals if BCD mode. Check using CMOSIsBcd().
//Ex: if bcd mode and we want 12/30, 10:45:15 -- 0x12/0x30, 0x10:0x45:0x15.
//Pass year as double digit number (obviously 20XX is too big for a U8).
        U8 *b = ds;

        PUSHFD
        CLI
        while (LBts(&sys_semas[SEMA_SYS_DATE], 0))
                PAUSE

        while (CMOSRegRead(CMOSR_STATUS_A) & CMOSF_UPDATING)
                PAUSE

        CMOSRegWrite(CMOSR_SEC,                         b[2]);
        CMOSRegWrite(CMOSR_MIN,                         b[3]);
        CMOSRegWrite(CMOSR_HOUR,                        b[4]);
        CMOSRegWrite(CMOSR_DAY_OF_WEEK,         b[5]);
        CMOSRegWrite(CMOSR_DAY_OF_MONTH,        b[6]);
        CMOSRegWrite(CMOSR_MONTH,                       b[7]);
        CMOSRegWrite(CMOSR_YEAR,                        b[8]);

        LBtr(&sys_semas[SEMA_SYS_DATE], 0);
        POPFD
}