1 /* 2 * This file is in the public domain, so clarified as of 3 * 1996-06-05 by Arthur David Olson. 4 */ 5 6 /* 7 * The whole implementation of `timesub()` and its dependencies are copied from 8 * `contrib/tzcode/localtime.c` as of FreeBSD commit 9 * 28f617de7d9b9c708eacb3c2c13e5287e1b7354d. 10 */ 11 12 #include <linux/time.h> 13 14 enum { 15 SECSPERMIN = 60, 16 MINSPERHOUR = 60, 17 SECSPERHOUR = SECSPERMIN * MINSPERHOUR, 18 HOURSPERDAY = 24, 19 DAYSPERWEEK = 7, 20 DAYSPERNYEAR = 365, 21 DAYSPERLYEAR = DAYSPERNYEAR + 1, 22 MONSPERYEAR = 12, 23 YEARSPERREPEAT = 400 /* years before a Gregorian repeat */ 24 }; 25 26 enum { 27 TM_SUNDAY, 28 TM_MONDAY, 29 TM_TUESDAY, 30 TM_WEDNESDAY, 31 TM_THURSDAY, 32 TM_FRIDAY, 33 TM_SATURDAY 34 }; 35 36 enum { 37 TM_JANUARY, 38 TM_FEBRUARY, 39 TM_MARCH, 40 TM_APRIL, 41 TM_MAY, 42 TM_JUNE, 43 TM_JULY, 44 TM_AUGUST, 45 TM_SEPTEMBER, 46 TM_OCTOBER, 47 TM_NOVEMBER, 48 TM_DECEMBER 49 }; 50 51 enum { 52 TM_YEAR_BASE = 1900, 53 TM_WDAY_BASE = TM_MONDAY, 54 EPOCH_YEAR = 1970, 55 EPOCH_WDAY = TM_THURSDAY 56 }; 57 58 #define SECSPERDAY (SECSPERHOUR * HOURSPERDAY) 59 #define DAYSPERREPEAT (400 * 365 + 100 - 4 + 1) 60 61 #define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) 62 63 #define TYPE_SIGNED(type) (((type) -1) < 0) 64 65 static const int mon_lengths[2][MONSPERYEAR] = { 66 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, 67 { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } 68 }; 69 70 static const int year_lengths[2] = { 71 DAYSPERNYEAR, DAYSPERLYEAR 72 }; 73 74 static time_t 75 leaps_thru_end_of_nonneg(time_t y) 76 { 77 return y / 4 - y / 100 + y / 400; 78 } 79 80 static time_t 81 leaps_thru_end_of(time_t y) 82 { 83 return (y < 0 84 ? -1 - leaps_thru_end_of_nonneg(-1 - y) 85 : leaps_thru_end_of_nonneg(y)); 86 } 87 88 static struct tm * 89 timesub(const time64_t *timep, int offset, struct tm *tmp) 90 { 91 time_t tdays; 92 const int * ip; 93 int corr; 94 int idays, rem, dayoff, dayrem; 95 time_t y; 96 97 /* If less than SECSPERMIN, the number of seconds since the 98 most recent positive leap second; otherwise, do not add 1 99 to localtime tm_sec because of leap seconds. */ 100 time_t secs_since_posleap = SECSPERMIN; 101 102 corr = 0; 103 104 /* Calculate the year, avoiding integer overflow even if 105 time_t is unsigned. */ 106 tdays = *timep / SECSPERDAY; 107 rem = *timep % SECSPERDAY; 108 rem += offset % SECSPERDAY - corr % SECSPERDAY + 3 * SECSPERDAY; 109 dayoff = offset / SECSPERDAY - corr / SECSPERDAY + rem / SECSPERDAY - 3; 110 rem %= SECSPERDAY; 111 /* y = (EPOCH_YEAR 112 + floor((tdays + dayoff) / DAYSPERREPEAT) * YEARSPERREPEAT), 113 sans overflow. But calculate against 1570 (EPOCH_YEAR - 114 YEARSPERREPEAT) instead of against 1970 so that things work 115 for localtime values before 1970 when time_t is unsigned. */ 116 dayrem = tdays % DAYSPERREPEAT; 117 dayrem += dayoff % DAYSPERREPEAT; 118 y = (EPOCH_YEAR - YEARSPERREPEAT 119 + ((1 + dayoff / DAYSPERREPEAT + dayrem / DAYSPERREPEAT 120 - ((dayrem % DAYSPERREPEAT) < 0) 121 + tdays / DAYSPERREPEAT) 122 * YEARSPERREPEAT)); 123 /* idays = (tdays + dayoff) mod DAYSPERREPEAT, sans overflow. */ 124 idays = tdays % DAYSPERREPEAT; 125 idays += dayoff % DAYSPERREPEAT + 2 * DAYSPERREPEAT; 126 idays %= DAYSPERREPEAT; 127 /* Increase Y and decrease IDAYS until IDAYS is in range for Y. */ 128 while (year_lengths[isleap(y)] <= idays) { 129 int tdelta = idays / DAYSPERLYEAR; 130 int_fast32_t ydelta = tdelta + !tdelta; 131 time_t newy = y + ydelta; 132 register int leapdays; 133 leapdays = leaps_thru_end_of(newy - 1) - 134 leaps_thru_end_of(y - 1); 135 idays -= ydelta * DAYSPERNYEAR; 136 idays -= leapdays; 137 y = newy; 138 } 139 140 if (!TYPE_SIGNED(time_t) && y < TM_YEAR_BASE) { 141 int signed_y = y; 142 tmp->tm_year = signed_y - TM_YEAR_BASE; 143 } else if ((!TYPE_SIGNED(time_t) || INT_MIN + TM_YEAR_BASE <= y) 144 && y - TM_YEAR_BASE <= INT_MAX) 145 tmp->tm_year = y - TM_YEAR_BASE; 146 else { 147 return NULL; 148 } 149 tmp->tm_yday = idays; 150 /* 151 ** The "extra" mods below avoid overflow problems. 152 */ 153 tmp->tm_wday = (TM_WDAY_BASE 154 + ((tmp->tm_year % DAYSPERWEEK) 155 * (DAYSPERNYEAR % DAYSPERWEEK)) 156 + leaps_thru_end_of(y - 1) 157 - leaps_thru_end_of(TM_YEAR_BASE - 1) 158 + idays); 159 tmp->tm_wday %= DAYSPERWEEK; 160 if (tmp->tm_wday < 0) 161 tmp->tm_wday += DAYSPERWEEK; 162 tmp->tm_hour = rem / SECSPERHOUR; 163 rem %= SECSPERHOUR; 164 tmp->tm_min = rem / SECSPERMIN; 165 tmp->tm_sec = rem % SECSPERMIN; 166 167 /* Use "... ??:??:60" at the end of the localtime minute containing 168 the second just before the positive leap second. */ 169 tmp->tm_sec += secs_since_posleap <= tmp->tm_sec; 170 171 ip = mon_lengths[isleap(y)]; 172 for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon)) 173 idays -= ip[tmp->tm_mon]; 174 tmp->tm_mday = idays + 1; 175 tmp->tm_isdst = 0; 176 return tmp; 177 } 178 179 void 180 linuxkpi_time64_to_tm(time64_t totalsecs, int offset, struct tm *result) 181 { 182 timesub(&totalsecs, offset, result); 183 } 184