2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2006 Poul-Henning Kamp
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * Convert MS-DOS FAT format timestamps to and from unix timespecs
32 * FAT filestamps originally consisted of two 16 bit integers, encoded like
35 * yyyyyyymmmmddddd (year - 1980, month, day)
37 * hhhhhmmmmmmsssss (hour, minutes, seconds divided by two)
39 * Subsequently even Microsoft realized that files could be accessed in less
40 * than two seconds and a byte was added containing:
42 * sfffffff (second mod two, 100ths of second)
44 * FAT timestamps are in the local timezone, with no indication of which
45 * timezone much less if daylight savings time applies.
47 * Later on again, in Windows NT, timestamps were defined relative to GMT.
49 * Purists will point out that UTC replaced GMT for such uses around
50 * half a century ago, already then. Ironically "NT" was an abbreviation of
51 * "New Technology". Anyway...
53 * The 'utc' argument determines if the resulting FATTIME timestamp
54 * should be on the UTC or local timezone calendar.
56 * The conversion functions below cut time into four-year leap-year
57 * cycles rather than single years and uses table lookups inside those
58 * cycles to get the months and years sorted out.
60 * Obviously we cannot calculate the correct table index going from
61 * a posix seconds count to Y/M/D, but we can get pretty close by
62 * dividing the daycount by 32 (giving a too low index), and then
63 * adjusting upwards a couple of steps if necessary.
65 * FAT timestamps have 7 bits for the year and starts at 1980, so
66 * they can represent up to 2107 which means that the non-leap-year
67 * 2100 must be handled.
69 * XXX: As long as time_t is 32 bits this is not relevant or easily
70 * XXX: testable. Revisit when time_t grows bigger.
71 * XXX: grepfodder: 64 bit time_t, y2100, y2.1k, 2100, leap year
75 #include <sys/param.h>
76 #include <sys/types.h>
79 #define DAY (24 * 60 * 60) /* Length of day in seconds */
80 #define YEAR 365 /* Length of normal year */
81 #define LYC (4 * YEAR + 1) /* Length of 4 year leap-year cycle */
82 #define T1980 (10 * 365 + 2) /* Days from 1970 to 1980 */
84 /* End of month is N days from start of (normal) year */
86 #define FEB (JAN + 28)
87 #define MAR (FEB + 31)
88 #define APR (MAR + 30)
89 #define MAY (APR + 31)
90 #define JUN (MAY + 30)
91 #define JUL (JUN + 31)
92 #define AUG (JUL + 31)
93 #define SEP (AUG + 30)
94 #define OCT (SEP + 31)
95 #define NOV (OCT + 30)
96 #define DEC (NOV + 31)
98 /* Table of months in a 4 year leap-year cycle */
100 #define ENC(y,m) (((y) << 9) | ((m) << 5))
102 static const struct {
103 uint16_t days
; /* month start in days relative to cycle */
104 uint16_t coded
; /* encoded year + month information */
106 { 0 + 0 * YEAR
, ENC(0, 1) },
108 { JAN
+ 0 * YEAR
, ENC(0, 2) }, { FEB
+ 0 * YEAR
+ 1, ENC(0, 3) },
109 { MAR
+ 0 * YEAR
+ 1, ENC(0, 4) }, { APR
+ 0 * YEAR
+ 1, ENC(0, 5) },
110 { MAY
+ 0 * YEAR
+ 1, ENC(0, 6) }, { JUN
+ 0 * YEAR
+ 1, ENC(0, 7) },
111 { JUL
+ 0 * YEAR
+ 1, ENC(0, 8) }, { AUG
+ 0 * YEAR
+ 1, ENC(0, 9) },
112 { SEP
+ 0 * YEAR
+ 1, ENC(0, 10) }, { OCT
+ 0 * YEAR
+ 1, ENC(0, 11) },
113 { NOV
+ 0 * YEAR
+ 1, ENC(0, 12) }, { DEC
+ 0 * YEAR
+ 1, ENC(1, 1) },
115 { JAN
+ 1 * YEAR
+ 1, ENC(1, 2) }, { FEB
+ 1 * YEAR
+ 1, ENC(1, 3) },
116 { MAR
+ 1 * YEAR
+ 1, ENC(1, 4) }, { APR
+ 1 * YEAR
+ 1, ENC(1, 5) },
117 { MAY
+ 1 * YEAR
+ 1, ENC(1, 6) }, { JUN
+ 1 * YEAR
+ 1, ENC(1, 7) },
118 { JUL
+ 1 * YEAR
+ 1, ENC(1, 8) }, { AUG
+ 1 * YEAR
+ 1, ENC(1, 9) },
119 { SEP
+ 1 * YEAR
+ 1, ENC(1, 10) }, { OCT
+ 1 * YEAR
+ 1, ENC(1, 11) },
120 { NOV
+ 1 * YEAR
+ 1, ENC(1, 12) }, { DEC
+ 1 * YEAR
+ 1, ENC(2, 1) },
122 { JAN
+ 2 * YEAR
+ 1, ENC(2, 2) }, { FEB
+ 2 * YEAR
+ 1, ENC(2, 3) },
123 { MAR
+ 2 * YEAR
+ 1, ENC(2, 4) }, { APR
+ 2 * YEAR
+ 1, ENC(2, 5) },
124 { MAY
+ 2 * YEAR
+ 1, ENC(2, 6) }, { JUN
+ 2 * YEAR
+ 1, ENC(2, 7) },
125 { JUL
+ 2 * YEAR
+ 1, ENC(2, 8) }, { AUG
+ 2 * YEAR
+ 1, ENC(2, 9) },
126 { SEP
+ 2 * YEAR
+ 1, ENC(2, 10) }, { OCT
+ 2 * YEAR
+ 1, ENC(2, 11) },
127 { NOV
+ 2 * YEAR
+ 1, ENC(2, 12) }, { DEC
+ 2 * YEAR
+ 1, ENC(3, 1) },
129 { JAN
+ 3 * YEAR
+ 1, ENC(3, 2) }, { FEB
+ 3 * YEAR
+ 1, ENC(3, 3) },
130 { MAR
+ 3 * YEAR
+ 1, ENC(3, 4) }, { APR
+ 3 * YEAR
+ 1, ENC(3, 5) },
131 { MAY
+ 3 * YEAR
+ 1, ENC(3, 6) }, { JUN
+ 3 * YEAR
+ 1, ENC(3, 7) },
132 { JUL
+ 3 * YEAR
+ 1, ENC(3, 8) }, { AUG
+ 3 * YEAR
+ 1, ENC(3, 9) },
133 { SEP
+ 3 * YEAR
+ 1, ENC(3, 10) }, { OCT
+ 3 * YEAR
+ 1, ENC(3, 11) },
134 { NOV
+ 3 * YEAR
+ 1, ENC(3, 12) }
139 timespec2fattime(const struct timespec
*tsp
, int utc
, uint16_t *ddp
,
140 uint16_t *dtp
, uint8_t *dhp
)
147 t1
-= 0; // XXX utc_offset();
150 *dhp
= (tsp
->tv_sec
& 1) * 100 + tsp
->tv_nsec
/ 10000000;
152 *dtp
= (t1
/ 2) % 30;
153 *dtp
|= ((t1
/ 60) % 60) << 5;
154 *dtp
|= ((t1
/ 3600) % 24) << 11;
159 /* Impossible date, truncate to 1980-01-01 */
165 * 2100 is not a leap year.
166 * XXX: a 32 bit time_t can not get us here.
168 if (t2
>= ((2100 - 1980) / 4 * LYC
+ FEB
))
171 /* Account for full leapyear cycles */
176 /* Find approximate table entry */
179 /* Find correct table entry */
180 while (m
< 47 && mtab
[m
+ 1].days
<= t2
)
183 /* Get year + month from the table */
184 *ddp
+= mtab
[m
].coded
;
186 /* And apply the day in the month */
187 t2
-= mtab
[m
].days
- 1;
194 * Table indexed by the bottom two bits of year + four bits of the month
195 * from the FAT timestamp, returning number of days into 4 year long
199 #define DCOD(m, y, l) ((m) + YEAR * (y) + (l))
200 static const uint16_t daytab
[64] = {
201 0, DCOD( 0, 0, 0), DCOD(JAN
, 0, 0), DCOD(FEB
, 0, 1),
202 DCOD(MAR
, 0, 1), DCOD(APR
, 0, 1), DCOD(MAY
, 0, 1), DCOD(JUN
, 0, 1),
203 DCOD(JUL
, 0, 1), DCOD(AUG
, 0, 1), DCOD(SEP
, 0, 1), DCOD(OCT
, 0, 1),
204 DCOD(NOV
, 0, 1), DCOD(DEC
, 0, 1), 0, 0,
205 0, DCOD( 0, 1, 1), DCOD(JAN
, 1, 1), DCOD(FEB
, 1, 1),
206 DCOD(MAR
, 1, 1), DCOD(APR
, 1, 1), DCOD(MAY
, 1, 1), DCOD(JUN
, 1, 1),
207 DCOD(JUL
, 1, 1), DCOD(AUG
, 1, 1), DCOD(SEP
, 1, 1), DCOD(OCT
, 1, 1),
208 DCOD(NOV
, 1, 1), DCOD(DEC
, 1, 1), 0, 0,
209 0, DCOD( 0, 2, 1), DCOD(JAN
, 2, 1), DCOD(FEB
, 2, 1),
210 DCOD(MAR
, 2, 1), DCOD(APR
, 2, 1), DCOD(MAY
, 2, 1), DCOD(JUN
, 2, 1),
211 DCOD(JUL
, 2, 1), DCOD(AUG
, 2, 1), DCOD(SEP
, 2, 1), DCOD(OCT
, 2, 1),
212 DCOD(NOV
, 2, 1), DCOD(DEC
, 2, 1), 0, 0,
213 0, DCOD( 0, 3, 1), DCOD(JAN
, 3, 1), DCOD(FEB
, 3, 1),
214 DCOD(MAR
, 3, 1), DCOD(APR
, 3, 1), DCOD(MAY
, 3, 1), DCOD(JUN
, 3, 1),
215 DCOD(JUL
, 3, 1), DCOD(AUG
, 3, 1), DCOD(SEP
, 3, 1), DCOD(OCT
, 3, 1),
216 DCOD(NOV
, 3, 1), DCOD(DEC
, 3, 1), 0, 0
220 fattime2timespec(unsigned dd
, unsigned dt
, unsigned dh
, int utc
,
221 struct timespec
*tsp
)
225 /* Unpack time fields */
226 tsp
->tv_sec
= (dt
& 0x1f) << 1;
227 tsp
->tv_sec
+= ((dt
& 0x7e0) >> 5) * 60;
228 tsp
->tv_sec
+= ((dt
& 0xf800) >> 11) * 3600;
229 tsp
->tv_sec
+= dh
/ 100;
230 tsp
->tv_nsec
= (dh
% 100) * 10000000;
233 day
= (dd
& 0x1f) - 1;
235 /* Full leap-year cycles */
236 day
+= LYC
* ((dd
>> 11) & 0x1f);
238 /* Month offset from leap-year cycle */
239 day
+= daytab
[(dd
>> 5) & 0x3f];
242 * 2100 is not a leap year.
243 * XXX: a 32 bit time_t can not get us here.
245 if (day
>= ((2100 - 1980) / 4 * LYC
+ FEB
))
248 /* Align with time_t epoch */
251 tsp
->tv_sec
+= DAY
* day
;
253 tsp
->tv_sec
+= 0; // XXX utc_offset();
263 main(int argc __unused
, char **argv __unused
)
273 for (i
= 0; i
< 10000; i
++) {
275 ts
.tv_sec
= random();
276 } while (ts
.tv_sec
< T1980
* 86400);
277 ts
.tv_nsec
= random() % 1000000000;
279 printf("%10d.%03ld -- ", ts
.tv_sec
, ts
.tv_nsec
/ 1000000);
281 gmtime_r(&ts
.tv_sec
, &tm
);
282 strftime(buf
, sizeof buf
, "%Y %m %d %H %M %S", &tm
);
283 printf("%s -- ", buf
);
285 a
= ts
.tv_sec
+ ts
.tv_nsec
* 1e-9;
287 timet2fattime(&ts
, &d
, &t
, &p
);
288 printf("%04x %04x %02x -- ", d
, t
, p
);
289 printf("%3d %02d %02d %02d %02d %02d -- ",
290 ((d
>> 9) & 0x7f) + 1980,
295 ((t
>> 0) & 0x1f) * 2);
297 ts
.tv_sec
= ts
.tv_nsec
= 0;
298 fattime2timet(d
, t
, p
, &ts
);
299 printf("%10d.%03ld == ", ts
.tv_sec
, ts
.tv_nsec
/ 1000000);
300 gmtime_r(&ts
.tv_sec
, &tm
);
301 strftime(buf
, sizeof buf
, "%Y %m %d %H %M %S", &tm
);
302 printf("%s -- ", buf
);
303 a
-= ts
.tv_sec
+ ts
.tv_nsec
* 1e-9;
310 #endif /* TEST_DRIVER */