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