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 52264684Skib * should be on the UTC or local timezone calendar. 53163611Sphk * 54266368Sian * The conversion functions below cut time into four-year leap-year 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