diff options
Diffstat (limited to 'unixtime.h')
-rw-r--r-- | unixtime.h | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/unixtime.h b/unixtime.h new file mode 100644 index 0000000..7dc4824 --- /dev/null +++ b/unixtime.h @@ -0,0 +1,130 @@ +#ifndef UNIXTIME_H +#define UNIXTIME_H + +#include <stdbool.h> +#include <stdint.h> + +#include "dlog.h" + +#define UNIX_EPOCH_YEAR 1970 +#define DOS_EPOCH_YEAR 1980 + +static bool is_leap_year(unsigned year) +{ + return ((year % 4) == 0 && (year % 100) != 0 || (year % 400) == 0); +} + +static int days_per_year(unsigned year) +{ + return is_leap_year(year) ? 366 : 365; +} + +static int days_per_month(unsigned month, bool leapyear) +{ + switch (month) { + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12: + return 31; + case 4: + case 6: + case 9: + case 11: + return 30; + case 2: + return leapyear ? 29 : 28; + } + + return 0; +} + +static void timestampns_to_dos_time(uint16_t __far *dos_time, uint16_t __far *dos_date, int64_t timestampns, int32_t tzoffset) +{ + // To maximize range of 32-bit ints, we'll use DOS's "2 seconds" as a unit instead of 1 second + long days_since_epoch = 0; + unsigned hours = 0, minutes = 0, seconds2 = 0; + unsigned year, month, day; + int per_year, per_month; + bool is_leap; + + __asm { + push eax /* Preserve 32-bit regs */ + push ecx + push edx + mov eax, dword ptr [timestampns] + mov edx, dword ptr [timestampns + 4] + mov ecx, 2 * 1000000000 /* nanoseconds in 2 seconds, should still fit in a dword */ + + idiv ecx /* 64-bit signed divison edx:eax / ecx, returns quotient in eax, remainder in edx */ + /* TODO: Handle overflow, which will trigger an interrupt */ + + /* eax now contains seconds_since_epoch / 2 */ + xor edx, edx /* Discard the remainder (less than 2 seconds) */ + + add eax, [tzoffset] /* Add tzoffset now (which is in seconds / 2 units) */ + + mov ecx, (24 * 60 * 60) / 2 /* seconds in one day / 2 */ + + idiv ecx + /* eax now contains days_since_epoch */ + /* edx contains seconds (since start of day) /2 , this now fits in a 16-bit word */ + + mov [days_since_epoch], eax + + mov eax, edx /* eax = seconds / 2 */ + xor edx, edx + mov ecx, 60 / 2 /* seconds in a minute / 2; also clears upper part of ecx, we'll start using 16-bit registers from now on */ + + div cx + /* ax contains minutes, dx contains remainder seconds/2 -- should be < 30 */ + mov [seconds2], dx + + xor dx, dx + mov cx, 60 /* minutes per hour */ + div cx + /* ax contains hours (< 24), dx contains remainder minutes (< 60) */ + mov [minutes], dx + mov [hours], ax + + pop edx + pop ecx + pop eax + } + + year = UNIX_EPOCH_YEAR; + if (days_since_epoch > 0) { + while (days_since_epoch >= (per_year = days_per_year(year))) { + days_since_epoch -= per_year; + year++; + } + } else { + while (days_since_epoch < 0) { + days_since_epoch += days_per_year(year); + year--; + } + } + + month = 1; + is_leap = is_leap_year(year); + while (days_since_epoch >= (per_month = days_per_month(month, is_leap))) { + days_since_epoch -= per_month; + month++; + } + day = 1 + days_since_epoch; // (day is 1-based) + + if (year < DOS_EPOCH_YEAR) { + dlog_puts("Year is too old, will show as 0"); + year = 0; + } else { + year -= DOS_EPOCH_YEAR; + } + + *dos_time = ((hours << 11) & 0xF800) | ((minutes << 5) & 0x7E0) | (seconds2 & 0x1F); + *dos_date = ((year << 9) & 0xFE00) | ((month << 5) & 0x1E0) | (day & 0x1F); +} + +#endif // UNIXTIME_H |