//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 value from CMOS register. See CMOS Registers.
    OutU8(CMOS_SEL, register);

    return InU8(CMOS_DATA);
}

U0 CMOSRegWrite(I64 register, I64 val)
{//Write value 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
}