1 /* $OpenBSD: a_time_posix.c,v 1.4 2023/11/13 12:46:07 beck 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 #include <openssl/asn1.h> 30 31 #define SECS_PER_HOUR (int64_t)(60 * 60) 32 #define SECS_PER_DAY (int64_t)(24 * SECS_PER_HOUR) 33 34 /* 35 * Is a year/month/day combination valid, in the range from year 0000 36 * to 9999? 37 */ 38 static int 39 is_valid_date(int year, int month, int day) 40 { 41 int days_in_month; 42 if (day < 1 || month < 1 || year < 0 || year > 9999) 43 return 0; 44 switch (month) { 45 case 1: 46 case 3: 47 case 5: 48 case 7: 49 case 8: 50 case 10: 51 case 12: 52 days_in_month = 31; 53 break; 54 case 4: 55 case 6: 56 case 9: 57 case 11: 58 days_in_month = 30; 59 break; 60 case 2: 61 if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) 62 days_in_month = 29; 63 else 64 days_in_month = 28; 65 break; 66 default: 67 return 0; 68 } 69 return day <= days_in_month; 70 } 71 72 /* 73 * Is a time valid? Leap seconds of 60 are not considered valid, as 74 * the POSIX time in seconds does not include them. 75 */ 76 static int 77 is_valid_time(int hours, int minutes, int seconds) 78 { 79 return hours >= 0 && minutes >= 0 && seconds >= 0 && hours <= 23 && 80 minutes <= 59 && seconds <= 59; 81 } 82 83 /* Is a int64 time representing a time within our expected range? */ 84 static int 85 is_valid_epoch_time(int64_t time) 86 { 87 /* 0000-01-01 00:00:00 UTC to 9999-12-31 23:59:59 UTC */ 88 return (int64_t)-62167219200LL <= time && 89 time <= (int64_t)253402300799LL; 90 } 91 92 /* 93 * Inspired by algorithms presented in 94 * https://howardhinnant.github.io/date_algorithms.html 95 * (Public Domain) 96 */ 97 static int 98 posix_time_from_utc(int year, int month, int day, int hours, int minutes, 99 int seconds, int64_t *out_time) 100 { 101 int64_t era, year_of_era, day_of_year, day_of_era, posix_days; 102 103 if (!is_valid_date(year, month, day) || 104 !is_valid_time(hours, minutes, seconds)) 105 return 0; 106 if (month <= 2) 107 year--; /* Start years on Mar 1, so leap days end a year. */ 108 109 /* At this point year will be in the range -1 and 9999.*/ 110 era = (year >= 0 ? year : year - 399) / 400; 111 year_of_era = year - era * 400; 112 day_of_year = (153 * (month > 2 ? month - 3 : month + 9) + 2) / 113 5 + day - 1; 114 day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 115 100 + day_of_year; 116 posix_days = era * 146097 + day_of_era - 719468; 117 *out_time = posix_days * SECS_PER_DAY + hours * SECS_PER_HOUR + 118 minutes * 60 + seconds; 119 120 return 1; 121 } 122 123 /* 124 * Inspired by algorithms presented in 125 * https://howardhinnant.github.io/date_algorithms.html 126 * (Public Domain) 127 */ 128 static int 129 utc_from_posix_time(int64_t time, int *out_year, int *out_month, int *out_day, 130 int *out_hours, int *out_minutes, int *out_seconds) 131 { 132 int64_t days, leftover_seconds, era, day_of_era, year_of_era, 133 day_of_year, month_of_year; 134 135 if (!is_valid_epoch_time(time)) 136 return 0; 137 138 days = time / SECS_PER_DAY; 139 leftover_seconds = time % SECS_PER_DAY; 140 if (leftover_seconds < 0) { 141 days--; 142 leftover_seconds += SECS_PER_DAY; 143 } 144 days += 719468; /* Shift to starting epoch of Mar 1 0000. */ 145 146 /* At this point, days will be in the range -61 and 3652364. */ 147 era = (days > 0 ? days : days - 146096) / 146097; 148 day_of_era = days - era * 146097; 149 year_of_era = (day_of_era - day_of_era / 1460 + day_of_era / 36524 - 150 day_of_era / 146096) / 151 365; 152 *out_year = year_of_era + era * 400; /* Year starts on Mar 1 */ 153 day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - 154 year_of_era / 100); 155 month_of_year = (5 * day_of_year + 2) / 153; 156 *out_month = (month_of_year < 10 ? month_of_year + 3 : 157 month_of_year - 9); 158 if (*out_month <= 2) 159 (*out_year)++; /* Adjust year back to Jan 1 start of year. */ 160 161 *out_day = day_of_year - (153 * month_of_year + 2) / 5 + 1; 162 *out_hours = leftover_seconds / SECS_PER_HOUR; 163 leftover_seconds %= SECS_PER_HOUR; 164 *out_minutes = leftover_seconds / 60; 165 *out_seconds = leftover_seconds % 60; 166 167 return 1; 168 } 169 170 static int 171 asn1_time_tm_to_posix(const struct tm *tm, int64_t *out) 172 { 173 /* Ensure additions below do not overflow */ 174 if (tm->tm_year > 9999) 175 return 0; 176 if (tm->tm_mon > 12) 177 return 0; 178 179 return posix_time_from_utc(tm->tm_year + 1900, tm->tm_mon + 1, 180 tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, out); 181 } 182 183 static int 184 asn1_time_posix_to_tm(int64_t time, struct tm *out_tm) 185 { 186 memset(out_tm, 0, sizeof(struct tm)); 187 if (!utc_from_posix_time(time, &out_tm->tm_year, &out_tm->tm_mon, 188 &out_tm->tm_mday, &out_tm->tm_hour, &out_tm->tm_min, 189 &out_tm->tm_sec)) 190 return 0; 191 192 out_tm->tm_year -= 1900; 193 out_tm->tm_mon -= 1; 194 195 return 1; 196 } 197 198 int 199 asn1_time_tm_to_time_t(const struct tm *tm, time_t *out) 200 { 201 int64_t posix_time; 202 203 if (!asn1_time_tm_to_posix(tm, &posix_time)) 204 return 0; 205 206 #ifdef SMALL_TIME_T 207 /* For portable. */ 208 if (sizeof(time_t) == sizeof(int32_t) && 209 (posix_time > INT32_MAX || posix_time < INT32_MIN)) 210 return 0; 211 #endif 212 213 *out = posix_time; 214 return 1; 215 } 216 217 int 218 asn1_time_time_t_to_tm(const time_t *time, struct tm *out_tm) 219 { 220 int64_t posix_time = *time; 221 222 return asn1_time_posix_to_tm(posix_time, out_tm); 223 } 224 225 int 226 OPENSSL_timegm(const struct tm *tm, time_t *out) { 227 return asn1_time_tm_to_time_t(tm, out); 228 } 229 LCRYPTO_ALIAS(OPENSSL_timegm); 230 231 struct tm * 232 OPENSSL_gmtime(const time_t *time, struct tm *out_tm) { 233 if (!asn1_time_time_t_to_tm(time, out_tm)) 234 return NULL; 235 return out_tm; 236 } 237 LCRYPTO_ALIAS(OPENSSL_gmtime); 238 239 int 240 OPENSSL_gmtime_adj(struct tm *tm, int off_day, long offset_sec) 241 { 242 int64_t posix_time; 243 244 /* Ensure additions below do not overflow */ 245 if (tm->tm_year > 9999) 246 return 0; 247 if (tm->tm_mon > 12) 248 return 0; 249 250 if (!posix_time_from_utc(tm->tm_year + 1900, tm->tm_mon + 1, 251 tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, &posix_time)) 252 return 0; 253 254 if (!utc_from_posix_time(posix_time + off_day * SECS_PER_DAY + 255 offset_sec, &tm->tm_year, &tm->tm_mon, &tm->tm_mday, &tm->tm_hour, 256 &tm->tm_min, &tm->tm_sec)) 257 return 0; 258 259 tm->tm_year -= 1900; 260 tm->tm_mon -= 1; 261 262 return 1; 263 } 264 265 int 266 OPENSSL_gmtime_diff(int *out_days, int *out_secs, const struct tm *from, 267 const struct tm *to) 268 { 269 int64_t time_to, time_from, timediff, daydiff; 270 271 if (!posix_time_from_utc(to->tm_year + 1900, to->tm_mon + 1, 272 to->tm_mday, to->tm_hour, to->tm_min, to->tm_sec, &time_to)) 273 return 0; 274 275 if (!posix_time_from_utc(from->tm_year + 1900, from->tm_mon + 1, 276 from->tm_mday, from->tm_hour, from->tm_min, 277 from->tm_sec, &time_from)) 278 return 0; 279 280 timediff = time_to - time_from; 281 daydiff = timediff / SECS_PER_DAY; 282 timediff %= SECS_PER_DAY; 283 if (daydiff > INT_MAX || daydiff < INT_MIN) 284 return 0; 285 286 *out_secs = timediff; 287 *out_days = daydiff; 288 289 return 1; 290 } 291