0078-time.c (3719B)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <time.h> 5 6 /* 7 output: 8 testing 9 50592 dates compared, all OK 10 done 11 end: 12 */ 13 14 15 #ifndef __unix__ 16 #define putenv(x) (x) 17 /* 18 * Ok, this is by definition undefined behaviour because 19 * we are using the name of a library function. The reallity 20 * is that if you use a static linker then this would 21 * overload the libc getenv and it will work as expected, 22 * but if you use a dynamic linker then the internal 23 * references will be tied to the internal getenv function 24 * and this would not work. For our main use case that is 25 * testing scc libc this is good enough, and with the 26 * fallback to putenv and unsetenv this will work in 27 * the systems we are interested on 28 */ 29 char * 30 getenv(const char *name) 31 { 32 if (strcmp(name, "TZ") != 0) 33 return NULL; 34 return "UTC"; 35 } 36 #endif 37 38 static long long 39 datetounix(long long year, int mon, int day, int hour, int min, int sec) 40 { 41 /* seconds in a month in a regular (non-leap) year */ 42 static const long secs_through_month[] = { 43 0, 31 * 86400, 59 * 86400, 90 * 86400, 44 120 * 86400, 151 * 86400, 181 * 86400, 212 * 86400, 45 243 * 86400, 273 * 86400, 304 * 86400, 334 * 86400 }; 46 int is_leap = 0, cycles, centuries = 0, leaps = 0, rem; 47 long long t; 48 49 /* optimization: handle common range year 1902 up to and including 2038 */ 50 if (year - 2ULL < 136) { 51 /* amount of leap days relative to 1970: every 4 years */ 52 leaps = (year - 68) >> 2; 53 if (!((year - 68) & 3)) { 54 leaps--; 55 is_leap = 1; 56 } else { 57 is_leap = 0; 58 } 59 t = 31536000 * (year - 70) + (86400 * leaps); /* 365 * 86400 = 31536000 */ 60 } else { 61 /* general leap year calculation: 62 leap years occur mostly every 4 years but every 100 years 63 a leap year is skipped unless the year is divisible by 400 */ 64 cycles = (year - 100) / 400; 65 rem = (year - 100) % 400; 66 if (rem < 0) { 67 cycles--; 68 rem += 400; 69 } 70 if (!rem) { 71 is_leap = 1; 72 } else { 73 if (rem >= 300) { 74 centuries = 3; 75 rem -= 300; 76 } else if (rem >= 200) { 77 centuries = 2; 78 rem -= 200; 79 } else if (rem >= 100) { 80 centuries = 1; 81 rem -= 100; 82 } 83 if (rem) { 84 leaps = rem / 4U; 85 rem %= 4U; 86 is_leap = !rem; 87 } 88 } 89 leaps += (97 * cycles) + (24 * centuries) - is_leap; 90 91 /* adjust 8 leap days from 1970 up to and including 2000: 92 ((30 * 365) + 8) * 86400 = 946771200 */ 93 t = ((year - 100) * 31536000LL) + (leaps * 86400LL) + 946771200LL; 94 } 95 t += secs_through_month[mon]; 96 if (is_leap && mon >= 2) 97 t += 86400; 98 t += 86400LL * (day - 1); 99 t += 3600LL * hour; 100 t += 60LL * min; 101 t += sec; 102 103 return t; 104 } 105 106 void 107 testtm(struct tm *tm) 108 { 109 time_t t1, t2; /* assume time_t is signed 64-bit */ 110 long long ll; 111 112 t1 = mktime(tm); 113 if (t1 == (time_t)-1) { 114 printf("timegm failed for %d, %d, %d, %d, %d, %d\n", 115 tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); 116 } 117 118 ll = datetounix(tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); 119 t2 = (time_t)ll; 120 121 if (t1 != t2) { 122 printf("result: %lld != %lld\n", (long long)t1, (long long)t2); 123 printf("for: %d, %d, %d, %d, %d, %d\n", 124 tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); 125 exit(1); 126 } 127 } 128 129 int 130 main(void) 131 { 132 struct tm tm; 133 int y, mon, d; 134 unsigned counter = 0; 135 136 puts("testing"); 137 putenv("TZ=UTC"); 138 139 for (y = 1902; y < 2038; y++) { 140 for (mon = 0; mon < 12; mon++) { 141 for (d = 1; d <= 31; d++) { 142 143 memset(&tm, 0, sizeof(tm)); 144 tm.tm_year = y - 1900; 145 tm.tm_mon = mon - 1; 146 tm.tm_mday = d; 147 tm.tm_hour = 0; 148 tm.tm_min = 0; 149 tm.tm_sec = 0; 150 tm.tm_isdst = -1; 151 152 testtm(&tm); 153 counter++; 154 } 155 } 156 } 157 printf("%u dates compared, all OK\n", counter); 158 puts("done"); 159 160 return 0; 161 }