1163611Sphk/*-
2163611Sphk * Copyright (c) 2006 Poul-Henning Kamp
3163611Sphk * All rights reserved.
4163611Sphk *
5163611Sphk * Redistribution and use in source and binary forms, with or without
6163611Sphk * modification, are permitted provided that the following conditions
7163611Sphk * are met:
8163611Sphk * 1. Redistributions of source code must retain the above copyright
9163611Sphk *    notice, this list of conditions and the following disclaimer.
10163611Sphk * 2. Redistributions in binary form must reproduce the above copyright
11163611Sphk *    notice, this list of conditions and the following disclaimer in the
12163611Sphk *    documentation and/or other materials provided with the distribution.
13163611Sphk *
14163611Sphk * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15163611Sphk * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16163611Sphk * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17163611Sphk * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18163611Sphk * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19163611Sphk * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20163611Sphk * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21163611Sphk * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22163611Sphk * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23163611Sphk * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24163611Sphk * SUCH DAMAGE.
25163611Sphk *
26163611Sphk * $FreeBSD$
27163611Sphk *
28163611Sphk * Convert MS-DOS FAT format timestamps to and from unix timespecs
29163611Sphk *
30163611Sphk * FAT filestamps originally consisted of two 16 bit integers, encoded like
31163611Sphk * this:
32163611Sphk *
33163611Sphk *	yyyyyyymmmmddddd (year - 1980, month, day)
34163611Sphk *
35163611Sphk *      hhhhhmmmmmmsssss (hour, minutes, seconds divided by two)
36163611Sphk *
37163611Sphk * Subsequently even Microsoft realized that files could be accessed in less
38163611Sphk * than two seconds and a byte was added containing:
39163611Sphk *
40163611Sphk *      sfffffff	 (second mod two, 100ths of second)
41163611Sphk *
42163611Sphk * FAT timestamps are in the local timezone, with no indication of which
43163611Sphk * timezone much less if daylight savings time applies.
44163611Sphk *
45163611Sphk * Later on again, in Windows NT, timestamps were defined relative to GMT.
46163611Sphk *
47163611Sphk * Purists will point out that UTC replaced GMT for such uses around
48163611Sphk * a century ago, already then.  Ironically "NT" was an abbreviation of
49163611Sphk * "New Technology".  Anyway...
50163611Sphk *
51163646Sphk * The 'utc' argument determines if the resulting FATTIME timestamp
52163646Sphk * should b on the UTC or local timezone calendar.
53163611Sphk *
54163611Sphk * The conversion functions below cut time into four-year leap-second
55163611Sphk * cycles rather than single years and uses table lookups inside those
56163611Sphk * cycles to get the months and years sorted out.
57163611Sphk *
58163611Sphk * Obviously we cannot calculate the correct table index going from
59163611Sphk * a posix seconds count to Y/M/D, but we can get pretty close by
60163611Sphk * dividing the daycount by 32 (giving a too low index), and then
61163611Sphk * adjusting upwards a couple of steps if necessary.
62163611Sphk *
63163611Sphk * FAT timestamps have 7 bits for the year and starts at 1980, so
64163611Sphk * they can represent up to 2107 which means that the non-leap-year
65163611Sphk * 2100 must be handled.
66163611Sphk *
67163611Sphk * XXX: As long as time_t is 32 bits this is not relevant or easily
68163611Sphk * XXX: testable.  Revisit when time_t grows bigger.
69163611Sphk * XXX: grepfodder: 64 bit time_t, y2100, y2.1k, 2100, leap year
70163611Sphk *
71163611Sphk */
72163611Sphk
73163611Sphk#include <sys/param.h>
74163611Sphk#include <sys/types.h>
75163611Sphk#include <sys/time.h>
76163611Sphk#include <sys/clock.h>
77163611Sphk
78163611Sphk#define DAY	(24 * 60 * 60)	/* Length of day in seconds */
79163611Sphk#define YEAR	365		/* Length of normal year */
80163611Sphk#define LYC	(4 * YEAR + 1)	/* Length of 4 year leap-year cycle */
81163611Sphk#define T1980	(10 * 365 + 2)	/* Days from 1970 to 1980 */
82163611Sphk
83163611Sphk/* End of month is N days from start of (normal) year */
84163611Sphk#define JAN	31
85163611Sphk#define FEB	(JAN + 28)
86163611Sphk#define MAR	(FEB + 31)
87163611Sphk#define APR	(MAR + 30)
88163611Sphk#define MAY	(APR + 31)
89163611Sphk#define JUN	(MAY + 30)
90163611Sphk#define JUL	(JUN + 31)
91163611Sphk#define AUG	(JUL + 31)
92163611Sphk#define SEP	(AUG + 30)
93163611Sphk#define OCT	(SEP + 31)
94163611Sphk#define NOV	(OCT + 30)
95163611Sphk#define DEC	(NOV + 31)
96163611Sphk
97163611Sphk/* Table of months in a 4 year leap-year cycle */
98163611Sphk
99163611Sphk#define ENC(y,m)	(((y) << 9) | ((m) << 5))
100163611Sphk
101163611Sphkstatic const struct {
102163611Sphk	uint16_t	days;	/* month start in days relative to cycle */
103163611Sphk	uint16_t	coded;	/* encoded year + month information */
104163611Sphk} mtab[48] = {
105163611Sphk	{   0 + 0 * YEAR,     ENC(0, 1)  },
106163611Sphk
107163611Sphk	{ JAN + 0 * YEAR,     ENC(0, 2)  }, { FEB + 0 * YEAR + 1, ENC(0, 3)  },
108163611Sphk	{ MAR + 0 * YEAR + 1, ENC(0, 4)  }, { APR + 0 * YEAR + 1, ENC(0, 5)  },
109163611Sphk	{ MAY + 0 * YEAR + 1, ENC(0, 6)  }, { JUN + 0 * YEAR + 1, ENC(0, 7)  },
110163611Sphk	{ JUL + 0 * YEAR + 1, ENC(0, 8)  }, { AUG + 0 * YEAR + 1, ENC(0, 9)  },
111163611Sphk	{ SEP + 0 * YEAR + 1, ENC(0, 10) }, { OCT + 0 * YEAR + 1, ENC(0, 11) },
112163611Sphk	{ NOV + 0 * YEAR + 1, ENC(0, 12) }, { DEC + 0 * YEAR + 1, ENC(1, 1)  },
113163611Sphk
114163611Sphk	{ JAN + 1 * YEAR + 1, ENC(1, 2)  }, { FEB + 1 * YEAR + 1, ENC(1, 3)  },
115163611Sphk	{ MAR + 1 * YEAR + 1, ENC(1, 4)  }, { APR + 1 * YEAR + 1, ENC(1, 5)  },
116163611Sphk	{ MAY + 1 * YEAR + 1, ENC(1, 6)  }, { JUN + 1 * YEAR + 1, ENC(1, 7)  },
117163611Sphk	{ JUL + 1 * YEAR + 1, ENC(1, 8)  }, { AUG + 1 * YEAR + 1, ENC(1, 9)  },
118163611Sphk	{ SEP + 1 * YEAR + 1, ENC(1, 10) }, { OCT + 1 * YEAR + 1, ENC(1, 11) },
119163611Sphk	{ NOV + 1 * YEAR + 1, ENC(1, 12) }, { DEC + 1 * YEAR + 1, ENC(2, 1)  },
120163611Sphk
121163611Sphk	{ JAN + 2 * YEAR + 1, ENC(2, 2)  }, { FEB + 2 * YEAR + 1, ENC(2, 3)  },
122163611Sphk	{ MAR + 2 * YEAR + 1, ENC(2, 4)  }, { APR + 2 * YEAR + 1, ENC(2, 5)  },
123163611Sphk	{ MAY + 2 * YEAR + 1, ENC(2, 6)  }, { JUN + 2 * YEAR + 1, ENC(2, 7)  },
124163611Sphk	{ JUL + 2 * YEAR + 1, ENC(2, 8)  }, { AUG + 2 * YEAR + 1, ENC(2, 9)  },
125163611Sphk	{ SEP + 2 * YEAR + 1, ENC(2, 10) }, { OCT + 2 * YEAR + 1, ENC(2, 11) },
126163611Sphk	{ NOV + 2 * YEAR + 1, ENC(2, 12) }, { DEC + 2 * YEAR + 1, ENC(3, 1)  },
127163611Sphk
128163611Sphk	{ JAN + 3 * YEAR + 1, ENC(3, 2)  }, { FEB + 3 * YEAR + 1, ENC(3, 3)  },
129163611Sphk	{ MAR + 3 * YEAR + 1, ENC(3, 4)  }, { APR + 3 * YEAR + 1, ENC(3, 5)  },
130163611Sphk	{ MAY + 3 * YEAR + 1, ENC(3, 6)  }, { JUN + 3 * YEAR + 1, ENC(3, 7)  },
131163611Sphk	{ JUL + 3 * YEAR + 1, ENC(3, 8)  }, { AUG + 3 * YEAR + 1, ENC(3, 9)  },
132163611Sphk	{ SEP + 3 * YEAR + 1, ENC(3, 10) }, { OCT + 3 * YEAR + 1, ENC(3, 11) },
133163611Sphk	{ NOV + 3 * YEAR + 1, ENC(3, 12) }
134163611Sphk};
135163611Sphk
136163611Sphk
137163611Sphkvoid
138209390Sedtimespec2fattime(struct timespec *tsp, int utc, uint16_t *ddp, uint16_t *dtp, uint8_t *dhp)
139163611Sphk{
140163611Sphk	time_t t1;
141163611Sphk	unsigned t2, l, m;
142163611Sphk
143163611Sphk	t1 = tsp->tv_sec;
144163646Sphk	if (!utc)
145163646Sphk		t1 -= utc_offset();
146163611Sphk
147163611Sphk	if (dhp != NULL)
148163611Sphk		*dhp = (tsp->tv_sec & 1) * 100 + tsp->tv_nsec / 10000000;
149163611Sphk	if (dtp != NULL) {
150163611Sphk		*dtp = (t1 / 2) % 30;
151163611Sphk		*dtp |= ((t1 / 60) % 60) << 5;
152163611Sphk		*dtp |= ((t1 / 3600) % 24) << 11;
153163611Sphk	}
154163611Sphk	if (ddp != NULL) {
155163611Sphk		t2 = t1 / DAY;
156163611Sphk		if (t2 < T1980) {
157163611Sphk			/* Impossible date, truncate to 1980-01-01 */
158163611Sphk			*ddp = 0x0021;
159163611Sphk		} else {
160163611Sphk			t2 -= T1980;
161163611Sphk
162163611Sphk			/*
163163611Sphk			 * 2100 is not a leap year.
164163611Sphk			 * XXX: a 32 bit time_t can not get us here.
165163611Sphk			 */
166163611Sphk			if (t2 >= ((2100 - 1980) / 4 * LYC + FEB))
167163611Sphk				t2++;
168163611Sphk
169163611Sphk			/* Account for full leapyear cycles */
170163611Sphk			l = t2 / LYC;
171163611Sphk			*ddp = (l * 4) << 9;
172163611Sphk			t2 -= l * LYC;
173163611Sphk
174163611Sphk			/* Find approximate table entry */
175163611Sphk			m = t2 / 32;
176163611Sphk
177163611Sphk			/* Find correct table entry */
178163611Sphk			while (m < 47 && mtab[m + 1].days <= t2)
179163611Sphk				m++;
180163611Sphk
181163611Sphk			/* Get year + month from the table */
182163611Sphk			*ddp += mtab[m].coded;
183163611Sphk
184163611Sphk			/* And apply the day in the month */
185163611Sphk			t2 -= mtab[m].days - 1;
186163611Sphk			*ddp |= t2;
187163611Sphk		}
188163611Sphk	}
189163611Sphk}
190163611Sphk
191163611Sphk/*
192163611Sphk * Table indexed by the bottom two bits of year + four bits of the month
193163611Sphk * from the FAT timestamp, returning number of days into 4 year long
194163611Sphk * leap-year cycle
195163611Sphk */
196163611Sphk
197163611Sphk#define DCOD(m, y, l)	((m) + YEAR * (y) + (l))
198163611Sphkstatic const uint16_t daytab[64] = {
199163611Sphk	0, 		 DCOD(  0, 0, 0), DCOD(JAN, 0, 0), DCOD(FEB, 0, 1),
200163611Sphk	DCOD(MAR, 0, 1), DCOD(APR, 0, 1), DCOD(MAY, 0, 1), DCOD(JUN, 0, 1),
201163611Sphk	DCOD(JUL, 0, 1), DCOD(AUG, 0, 1), DCOD(SEP, 0, 1), DCOD(OCT, 0, 1),
202163611Sphk	DCOD(NOV, 0, 1), DCOD(DEC, 0, 1), 0,               0,
203163611Sphk	0, 		 DCOD(  0, 1, 1), DCOD(JAN, 1, 1), DCOD(FEB, 1, 1),
204163611Sphk	DCOD(MAR, 1, 1), DCOD(APR, 1, 1), DCOD(MAY, 1, 1), DCOD(JUN, 1, 1),
205163611Sphk	DCOD(JUL, 1, 1), DCOD(AUG, 1, 1), DCOD(SEP, 1, 1), DCOD(OCT, 1, 1),
206163611Sphk	DCOD(NOV, 1, 1), DCOD(DEC, 1, 1), 0,               0,
207163611Sphk	0,		 DCOD(  0, 2, 1), DCOD(JAN, 2, 1), DCOD(FEB, 2, 1),
208163611Sphk	DCOD(MAR, 2, 1), DCOD(APR, 2, 1), DCOD(MAY, 2, 1), DCOD(JUN, 2, 1),
209163611Sphk	DCOD(JUL, 2, 1), DCOD(AUG, 2, 1), DCOD(SEP, 2, 1), DCOD(OCT, 2, 1),
210163611Sphk	DCOD(NOV, 2, 1), DCOD(DEC, 2, 1), 0,               0,
211163611Sphk	0,		 DCOD(  0, 3, 1), DCOD(JAN, 3, 1), DCOD(FEB, 3, 1),
212163611Sphk	DCOD(MAR, 3, 1), DCOD(APR, 3, 1), DCOD(MAY, 3, 1), DCOD(JUN, 3, 1),
213163611Sphk	DCOD(JUL, 3, 1), DCOD(AUG, 3, 1), DCOD(SEP, 3, 1), DCOD(OCT, 3, 1),
214163611Sphk	DCOD(NOV, 3, 1), DCOD(DEC, 3, 1), 0,               0
215163611Sphk};
216163611Sphk
217163611Sphkvoid
218163646Sphkfattime2timespec(unsigned dd, unsigned dt, unsigned dh, int utc, struct timespec *tsp)
219163611Sphk{
220163611Sphk	unsigned day;
221163611Sphk
222163611Sphk	/* Unpack time fields */
223163611Sphk	tsp->tv_sec = (dt & 0x1f) << 1;
224163611Sphk	tsp->tv_sec += ((dt & 0x7e0) >> 5) * 60;
225163611Sphk	tsp->tv_sec += ((dt & 0xf800) >> 11) * 3600;
226163611Sphk	tsp->tv_sec += dh / 100;
227163611Sphk	tsp->tv_nsec = (dh % 100) * 10000000;
228163611Sphk
229163611Sphk	/* Day of month */
230163611Sphk	day = (dd & 0x1f) - 1;
231163611Sphk
232163611Sphk	/* Full leap-year cycles */
233163611Sphk	day += LYC * ((dd >> 11) & 0x1f);
234163611Sphk
235163611Sphk	/* Month offset from leap-year cycle */
236163611Sphk	day += daytab[(dd >> 5) & 0x3f];
237163611Sphk
238163611Sphk	/*
239163611Sphk	 * 2100 is not a leap year.
240163611Sphk	 * XXX: a 32 bit time_t can not get us here.
241163611Sphk	 */
242163611Sphk	if (day >= ((2100 - 1980) / 4 * LYC + FEB))
243163611Sphk		day--;
244163611Sphk
245163611Sphk	/* Align with time_t epoch */
246163611Sphk	day += T1980;
247163611Sphk
248163611Sphk	tsp->tv_sec += DAY * day;
249163646Sphk	if (!utc)
250163646Sphk		tsp->tv_sec += utc_offset();
251163611Sphk}
252163611Sphk
253163611Sphk#ifdef TEST_DRIVER
254163611Sphk
255163611Sphk#include <stdio.h>
256163611Sphk#include <unistd.h>
257163611Sphk#include <stdlib.h>
258163611Sphk
259163611Sphkint
260163611Sphkmain(int argc __unused, char **argv __unused)
261163611Sphk{
262163611Sphk	int i;
263163611Sphk	struct timespec ts;
264163611Sphk	struct tm tm;
265163611Sphk	double a;
266209390Sed	uint16_t d, t;
267209390Sed	uint8_t p;
268163611Sphk	char buf[100];
269163611Sphk
270163611Sphk	for (i = 0; i < 10000; i++) {
271163611Sphk		do {
272163611Sphk			ts.tv_sec = random();
273163611Sphk		} while (ts.tv_sec < T1980 * 86400);
274163611Sphk		ts.tv_nsec = random() % 1000000000;
275163611Sphk
276163611Sphk		printf("%10d.%03ld -- ", ts.tv_sec, ts.tv_nsec / 1000000);
277163611Sphk
278163611Sphk		gmtime_r(&ts.tv_sec, &tm);
279163611Sphk		strftime(buf, sizeof buf, "%Y %m %d %H %M %S", &tm);
280163611Sphk		printf("%s -- ", buf);
281163611Sphk
282163611Sphk		a = ts.tv_sec + ts.tv_nsec * 1e-9;
283163611Sphk		d = t = p = 0;
284163611Sphk		timet2fattime(&ts, &d, &t, &p);
285163611Sphk		printf("%04x %04x %02x -- ", d, t, p);
286163611Sphk		printf("%3d %02d %02d %02d %02d %02d -- ",
287163611Sphk		    ((d >> 9)  & 0x7f) + 1980,
288163611Sphk		    (d >> 5)  & 0x0f,
289163611Sphk		    (d >> 0)  & 0x1f,
290163611Sphk		    (t >> 11) & 0x1f,
291163611Sphk		    (t >> 5)  & 0x3f,
292163611Sphk		    ((t >> 0)  & 0x1f) * 2);
293163611Sphk
294163611Sphk		ts.tv_sec = ts.tv_nsec = 0;
295163611Sphk		fattime2timet(d, t, p, &ts);
296163611Sphk		printf("%10d.%03ld == ", ts.tv_sec, ts.tv_nsec / 1000000);
297163611Sphk		gmtime_r(&ts.tv_sec, &tm);
298163611Sphk		strftime(buf, sizeof buf, "%Y %m %d %H %M %S", &tm);
299163611Sphk		printf("%s -- ", buf);
300163611Sphk		a -= ts.tv_sec + ts.tv_nsec * 1e-9;
301163611Sphk		printf("%.3f", a);
302163611Sphk		printf("\n");
303163611Sphk	}
304163611Sphk	return (0);
305163611Sphk}
306163611Sphk
307163611Sphk#endif /* TEST_DRIVER */
308