stardot / beebem-windows

BBC micro emulator for Windows
http://www.mkw.me.uk/beebem/
Other
88 stars 34 forks source link

Y2K(20) issue #111

Open ZXGuesser opened 1 year ago

ZXGuesser commented 1 year ago

Originally posted on Stardot but for some reason I never opened a bug here 🤦‍♂️

I've recently acquired a Master 128 so have been poking around in Master 128 emulation for the first time and (re)discovered a Y2K issue, that was presumably masked before by the "Y2K adjust" option. Was the aim of the Y2K adjust to keep dates in the 21st century, but after the 80s for old software's sake? If so it needs updating now!

The following modified code would seem to work to me for reading and setting the time, but maybe I'm missing something about how the RTC is supposed to work.

/*-------------------------------------------------------------------------*/
time_t CMOSConvertClock()
{
    struct tm *CurTime = localtime(&SysTime);

    struct tm Base;
    Base.tm_sec = BCDToBin(CMOSRAM[0]);
    Base.tm_min = BCDToBin(CMOSRAM[2]);
    Base.tm_hour = BCDToBin(CMOSRAM[4]);
    Base.tm_mday = BCDToBin(CMOSRAM[7]);
    Base.tm_mon = BCDToBin(CMOSRAM[8])-1;
    Base.tm_year = BCDToBin(CMOSRAM[9]);
    Base.tm_wday = -1;
    Base.tm_yday = -1;
    Base.tm_isdst = -1;

    Base.tm_year += (CurTime->tm_year / 100) * 100;

    return mktime(&Base);
}
/*-------------------------------------------------------------------------*/
void RTCInit()
{
    time(&SysTime);
    struct tm *CurTime = localtime(&SysTime);
    CMOSRAM[0] = BCD(static_cast<unsigned char>(CurTime->tm_sec));
    CMOSRAM[2] = BCD(static_cast<unsigned char>(CurTime->tm_min));
    CMOSRAM[4] = BCD(static_cast<unsigned char>(CurTime->tm_hour));
    CMOSRAM[6] = BCD(static_cast<unsigned char>(CurTime->tm_wday + 1));
    CMOSRAM[7] = BCD(static_cast<unsigned char>(CurTime->tm_mday));
    CMOSRAM[8] = BCD(static_cast<unsigned char>(CurTime->tm_mon + 1));
    CMOSRAM[9] = BCD(static_cast<unsigned char>((CurTime->tm_year - (RTCY2KAdjust ? 20 : 0)) % 100));
    RTCTimeOffset = SysTime - CMOSConvertClock();
}
/*-------------------------------------------------------------------------*/
void RTCUpdate()
{
    time(&SysTime);
    SysTime -= RTCTimeOffset;
    struct tm *CurTime = localtime(&SysTime);
    CMOSRAM[0] = BCD(static_cast<unsigned char>(CurTime->tm_sec));
    CMOSRAM[2] = BCD(static_cast<unsigned char>(CurTime->tm_min));
    CMOSRAM[4] = BCD(static_cast<unsigned char>(CurTime->tm_hour));
    CMOSRAM[6] = BCD(static_cast<unsigned char>(CurTime->tm_wday + 1));
    CMOSRAM[7] = BCD(static_cast<unsigned char>(CurTime->tm_mday));
    CMOSRAM[8] = BCD(static_cast<unsigned char>(CurTime->tm_mon + 1));
    CMOSRAM[9] = BCD(static_cast<unsigned char>(CurTime->tm_year % 100));
}
chrisn commented 1 year ago

Thanks! I had seen this on Stardot, but posting here is a helpful reminder to look at it.

I'm wondering whether the RTCY2KAdjust option is still needed. The previous code in RTCInit() is:

CMOSRAM[9] = BCD(static_cast<unsigned char>(CurTime->tm_year - (RTCY2KAdjust ? 20 : 0)));

tm_year contains the number of years since 1900, so 100 for the year 2000, which is larger than the maximum two-digit BCD number (99). So it seems that RTCY2KAdjust is intended to map years 2000-2019 to 1980-1999. But this obviously will overflow the BCD range from 2020 onwards.

As the MOS only looks for the last two digits of the year (info here), it seems we could just use the year modulo 100 and remove the RTCY2KAdjust option. Years will always be displayed as 19xx, unless using a patched MOS, though.

ZXGuesser commented 2 months ago

My suggestion for dealing with the "configuring RTC to return suitable years for specific things" issue would be to simply store and reload the offset from SysTime as part of the preferences (defaulting to zero offset so a new profile gets the current system time). That way the realtime clock still behaves in a predictable way but can be set to whatever date and time a user wants and persists when the emulated beeb is "off" just as a real machine.