aboutsummaryrefslogtreecommitdiff
path: root/unixtime.h
blob: fe5739453fd99311108722c1642da935980a53e7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#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;

	// Since we can only run on >= 386 anyway, let's do the initial
	// 64-bit division and the rest of 32-bit divisions/modulos
	// in asm using 386 32-bit instructions.

	__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) */

		sub eax, [tzoffset]          /* Subtract 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