1 /* $OpenBSD: a_time_posix.c,v 1.3 2023/01/01 16:58:23 miod Exp $ */ 2 /* 3 * Copyright (c) 2022, Google Inc. 4 * Copyright (c) 2022, Bob Beck <beck@obtuse.com> 5 * 6 * Permission to use, copy, modify, and/or distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 13 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 15 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 16 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 /* 20 * Time conversion to/from POSIX time_t and struct tm, with no support 21 * for time zones other than UTC 22 */ 23 24 #include <inttypes.h> 25 #include <limits.h> 26 #include <string.h> 27 #include <time.h> 28 29 #define SECS_PER_HOUR (int64_t)(60 * 60) 30 #define SECS_PER_DAY (int64_t)(24 * SECS_PER_HOUR) 31 32 /* 33 * Is a year/month/day combination valid, in the range from year 0000 34 * to 9999? 35 */ 36 static int 37 is_valid_date(int year, int month, int day) 38 { 39 int days_in_month; 40 if (day < 1 || month < 1 || year < 0 || year > 9999) 41 return 0; 42 switch (month) { 43 case 1: 44 case 3: 45 case 5: 46 case 7: 47 case 8: 48 case 10: 49 case 12: 50 days_in_month = 31; 51 break; 52 case 4: 53 case 6: 54 case 9: 55 case 11: 56 days_in_month = 30; 57 break; 58 case 2: 59 if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) 60 days_in_month = 29; 61 else 62 days_in_month = 28; 63 break; 64 default: 65 return 0; 66 } 67 return day <= days_in_month; 68 } 69 70 /* 71 * Is a time valid? Leap seconds of 60 are not considered valid, as 72 * the POSIX time in seconds does not include them. 73 */ 74 static int 75 is_valid_time(int hours, int minutes, int seconds) 76 { 77 return hours >= 0 && minutes >= 0 && seconds >= 0 && hours <= 23 && 78 minutes <= 59 && seconds <= 59; 79 } 80 81 /* Is a int64 time representing a time within our expected range? */ 82 static int 83 is_valid_epoch_time(int64_t time) 84 { 85 /* 0000-01-01 00:00:00 UTC to 9999-12-31 23:59:59 UTC */ 86 return (int64_t)-62167219200LL <= time && 87 time <= (int64_t)253402300799LL; 88 } 89 90 /* 91 * Inspired by algorithms presented in 92 * https://howardhinnant.github.io/date_algorithms.html 93 * (Public Domain) 94 */ 95 static int 96 posix_time_from_utc(int year, int month, int day, int hours, int minutes, 97 int seconds, int64_t *out_time) 98 { 99 int64_t era, year_of_era, day_of_year, day_of_era, posix_days; 100 101 if (!is_valid_date(year, month, day) || 102 !is_valid_time(hours, minutes, seconds)) 103 return 0; 104 if (month <= 2) 105 year--; /* Start years on Mar 1, so leap days end a year. */ 106 107 /* At this point year will be in the range -1 and 9999.*/ 108 era = (year >= 0 ? year : year - 399) / 400; 109 year_of_era = year - era * 400; 110 day_of_year = (153 * (month > 2 ? month - 3 : month + 9) + 2) / 111 5 + day - 1; 112 day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 113 100 + day_of_year; 114 posix_days = era * 146097 + day_of_era - 719468; 115 *out_time = posix_days * SECS_PER_DAY + hours * SECS_PER_HOUR + 116 minutes * 60 + seconds; 117 118 return 1; 119 } 120 121 /* 122 * Inspired by algorithms presented in 123 * https://howardhinnant.github.io/date_algorithms.html 124 * (Public Domain) 125 */ 126 static int 127 utc_from_posix_time(int64_t time, int *out_year, int *out_month, int *out_day, 128 int *out_hours, int *out_minutes, int *out_seconds) 129 { 130 int64_t days, leftover_seconds, era, day_of_era, year_of_era, 131 day_of_year, month_of_year; 132 133 if (!is_valid_epoch_time(time)) 134 return 0; 135 136 days = time / SECS_PER_DAY; 137 leftover_seconds = time % SECS_PER_DAY; 138 if (leftover_seconds < 0) { 139 days--; 140 leftover_seconds += SECS_PER_DAY; 141 } 142 days += 719468; /* Shift to starting epoch of Mar 1 0000. */ 143 144 /* At this point, days will be in the range -61 and 3652364. */ 145 era = (days > 0 ? days : days - 146096) / 146097; 146 day_of_era = days - era * 146097; 147 year_of_era = (day_of_era - day_of_era / 1460 + day_of_era / 36524 - 148 day_of_era / 146096) / 149 365; 150 *out_year = year_of_era + era * 400; /* Year starts on Mar 1 */ 151 day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - 152 year_of_era / 100); 153 month_of_year = (5 * day_of_year + 2) / 153; 154 *out_month = (month_of_year < 10 ? month_of_year + 3 : 155 month_of_year - 9); 156 if (*out_month <= 2) 157 (*out_year)++; /* Adjust year back to Jan 1 start of year. */ 158 159 *out_day = day_of_year - (153 * month_of_year + 2) / 5 + 1; 160 *out_hours = leftover_seconds / SECS_PER_HOUR; 161 leftover_seconds %= SECS_PER_HOUR; 162 *out_minutes = leftover_seconds / 60; 163 *out_seconds = leftover_seconds % 60; 164 165 return 1; 166 } 167 168 static int 169 asn1_time_tm_to_posix(const struct tm *tm, int64_t *out) 170 { 171 /* Ensure additions below do not overflow */ 172 if (tm->tm_year > 9999) 173 return 0; 174 if (tm->tm_mon > 12) 175 return 0; 176 177 return posix_time_from_utc(tm->tm_year + 1900, tm->tm_mon + 1, 178 tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, out); 179 } 180 181 static int 182 asn1_time_posix_to_tm(int64_t time, struct tm *out_tm) 183 { 184 memset(out_tm, 0, sizeof(struct tm)); 185 if (!utc_from_posix_time(time, &out_tm->tm_year, &out_tm->tm_mon, 186 &out_tm->tm_mday, &out_tm->tm_hour, &out_tm->tm_min, 187 &out_tm->tm_sec)) 188 return 0; 189 190 out_tm->tm_year -= 1900; 191 out_tm->tm_mon -= 1; 192 193 return 1; 194 } 195 196 int 197 asn1_time_tm_to_time_t(const struct tm *tm, time_t *out) 198 { 199 int64_t posix_time; 200 201 if (!asn1_time_tm_to_posix(tm, &posix_time)) 202 return 0; 203 204 #ifdef SMALL_TIME_T 205 /* For portable. */ 206 if (sizeof(time_t) == sizeof(int32_t) && 207 (posix_time > INT32_MAX || posix_time < INT32_MIN)) 208 return 0; 209 #endif 210 211 *out = posix_time; 212 return 1; 213 } 214 215 int 216 asn1_time_time_t_to_tm(const time_t *time, struct tm *out_tm) 217 { 218 int64_t posix_time = *time; 219 220 return asn1_time_posix_to_tm(posix_time, out_tm); 221 } 222 223 int 224 OPENSSL_gmtime_adj(struct tm *tm, int off_day, long offset_sec) 225 { 226 int64_t posix_time; 227 228 /* Ensure additions below do not overflow */ 229 if (tm->tm_year > 9999) 230 return 0; 231 if (tm->tm_mon > 12) 232 return 0; 233 234 if (!posix_time_from_utc(tm->tm_year + 1900, tm->tm_mon + 1, 235 tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, &posix_time)) 236 return 0; 237 238 if (!utc_from_posix_time(posix_time + off_day * SECS_PER_DAY + 239 offset_sec, &tm->tm_year, &tm->tm_mon, &tm->tm_mday, &tm->tm_hour, 240 &tm->tm_min, &tm->tm_sec)) 241 return 0; 242 243 tm->tm_year -= 1900; 244 tm->tm_mon -= 1; 245 246 return 1; 247 } 248 249 int 250 OPENSSL_gmtime_diff(int *out_days, int *out_secs, const struct tm *from, 251 const struct tm *to) 252 { 253 int64_t time_to, time_from, timediff, daydiff; 254 255 if (!posix_time_from_utc(to->tm_year + 1900, to->tm_mon + 1, 256 to->tm_mday, to->tm_hour, to->tm_min, to->tm_sec, &time_to)) 257 return 0; 258 259 if (!posix_time_from_utc(from->tm_year + 1900, from->tm_mon + 1, 260 from->tm_mday, from->tm_hour, from->tm_min, 261 from->tm_sec, &time_from)) 262 return 0; 263 264 timediff = time_to - time_from; 265 daydiff = timediff / SECS_PER_DAY; 266 timediff %= SECS_PER_DAY; 267 if (daydiff > INT_MAX || daydiff < INT_MIN) 268 return 0; 269 270 *out_secs = timediff; 271 *out_days = daydiff; 272 273 return 1; 274 } 275