ksgi
/
datetime.c
579 строк · 13.5 Кб
1/* $Id$ */
2/*
3* Copyright (c) 2016, 2017, 2020 Kristaps Dzonsons <kristaps@bsd.lv>
4*
5* Permission to use, copy, modify, and distribute this software for any
6* purpose with or without fee is hereby granted, provided that the above
7* copyright notice and this permission notice appear in all copies.
8*
9* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16*/
17#include "config.h"18
19#include <assert.h> /* debug */20#include <inttypes.h>21#include <limits.h>22#include <stdarg.h>23#include <stdint.h>24#include <stdio.h>25#include <stdlib.h>26#include <string.h>27#include <time.h>28
29#include "kcgi.h"30#include "extern.h"31
32/*
33* We'll never have khttp_epoch2str or khttp_epoch2ustr larger than
34* this, even with 64-bit time.
35*/
36#define MAX_TIME_STRING 6437
38struct tm64 {39int64_t tm_sec; /* seconds after the minute [0-60] */40int64_t tm_min; /* minutes after the hour [0-59] */41int64_t tm_hour; /* hours since midnight [0-23] */42int64_t tm_mday; /* day of the month [1-31] */43int64_t tm_mon; /* months since January [0-11] */44int64_t tm_year; /* years since 1900 */45int64_t tm_wday; /* days since Sunday [0-6] */46int64_t tm_yday; /* days since January 1 [0-365] */47};48
49/*
50* The following code is modified from newlib, the relevant parts being
51* licensed as follows:
52*
53* Copyright (c) 1994-2009 Red Hat, Inc. All rights reserved.
54*
55* This copyrighted material is made available to anyone wishing to use,
56* modify, copy, or redistribute it subject to the terms and conditions
57* of the BSD License. This program is distributed in the hope that
58* it will be useful, but WITHOUT ANY WARRANTY expressed or implied,
59* including the implied warranties of MERCHANTABILITY or FITNESS FOR
60* A PARTICULAR PURPOSE. A copy of this license is available at
61* http://www.opensource.org/licenses. Any Red Hat trademarks that are
62* incorporated in the source code or documentation are not subject to
63* the BSD License and may only be used or replicated with the express
64* permission of Red Hat, Inc.
65*/
66#define _SEC_IN_MINUTE 60L67#define _SEC_IN_HOUR 3600L68#define _SEC_IN_DAY 86400L69
70static const int DAYS_IN_MONTH[12] =71{31, 28, 31, 30, 31, 30,7231, 31, 30, 31, 30, 31};73
74#define _DAYS_IN_MONTH(x) \75((x == 1) ? days_in_feb : DAYS_IN_MONTH[x])76
77static const int _DAYS_BEFORE_MONTH[12] =78{0, 31, 59, 90, 120, 151,79181, 212, 243, 273, 304, 334};80
81#define _ISLEAP(y) \82(((y) % 4) == 0 && \83(((y) % 100) != 0 || (((y)+1900) % 400) == 0))84#define _DAYS_IN_YEAR(year) \85(_ISLEAP(year) ? 366 : 365)86
87/*
88* There are 97 leap years in 400-year periods. ((400 - 97) * 365 + 97 *
89* 366).
90*/
91#define DAYS_PER_ERA 146097L92
93/*
94* Make sure that all values are sane.
95* Return zero on failure, non-zero on success.
96*/
97static int98khttp_validate_time(const struct tm64 *tim_p)99{
100int64_t days_in_feb = 28;101
102if (tim_p->tm_sec < 0 || tim_p->tm_sec > 59)103return 0;104if (tim_p->tm_min < 0 || tim_p->tm_min > 59)105return 0;106if (tim_p->tm_hour < 0 || tim_p->tm_hour > 23)107return 0;108if (tim_p->tm_mon < 0 || tim_p->tm_mon > 11)109return 0;110
111/*112* This magic number is (more or less) the maximum number of
113* years that we can consider in a 64-bit year.
114* Outside of this we'll get overflow or underflow.
115*/
116
117if (tim_p->tm_year > 292277026596 ||118tim_p->tm_year < -292277024557)119return 0;120
121if (_DAYS_IN_YEAR(tim_p->tm_year) == 366)122days_in_feb = 29;123if (tim_p->tm_mday <= 0 ||124tim_p->tm_mday > _DAYS_IN_MONTH(tim_p->tm_mon))125return 0;126
127return 1;128}
129
130/*
131* Convert broken-down time to the UNIX epoch.
132* Returns zero if the broken-dwon values are not sane, non-zero
133* otherwise.
134* See khttp_validate_time().
135*/
136static int137khttp_mktime(int64_t *res, struct tm64 *tim_p)138{
139int64_t tim = 0, days = 0, year, maxyear, era;140
141/* Validate structure. */142
143if (!khttp_validate_time(tim_p))144return 0;145
146/* Compute hours, minutes, seconds. */147
148tim += tim_p->tm_sec +149(tim_p->tm_min * _SEC_IN_MINUTE) +150(tim_p->tm_hour * _SEC_IN_HOUR);151
152/* Compute days in year. */153
154days += tim_p->tm_mday - 1;155days += _DAYS_BEFORE_MONTH[tim_p->tm_mon];156
157if (tim_p->tm_mon > 1 && _DAYS_IN_YEAR(tim_p->tm_year) == 366)158days++;159
160/* Compute day of the year. */161
162tim_p->tm_yday = days;163
164/* Compute days in other years. */165/* WARNING: THIS IS VERY SLOW. */166
167if ((year = tim_p->tm_year) > 70) {168maxyear = tim_p->tm_year > 400 ? 400 : tim_p->tm_year;169for (year = 70; year < maxyear; year++)170days += _DAYS_IN_YEAR(year);171era = (tim_p->tm_year - year) / 400;172days += era * DAYS_PER_ERA;173year += era * 400;174for ( ; year < tim_p->tm_year; year++)175days += _DAYS_IN_YEAR(year);176} else if (year < 70) {177maxyear = tim_p->tm_year < -400 ? -400 : tim_p->tm_year;178for (year = 69; year > maxyear; year--)179days -= _DAYS_IN_YEAR(year);180era = (tim_p->tm_year - year) / 400;181assert(era <= 0);182days += era * DAYS_PER_ERA;183year += era * 400;184for ( ; year > tim_p->tm_year; year--)185days -= _DAYS_IN_YEAR(year);186days -= _DAYS_IN_YEAR(year);187}188
189/* Compute total seconds. */190
191tim += days * _SEC_IN_DAY;192
193/* Compute day of the week. */194
195if ((tim_p->tm_wday = (days + 4) % 7) < 0)196tim_p->tm_wday += 7;197
198*res = tim;199return 1;200}
201
202/*
203* Move epoch from 01.01.1970 to 01.03.0000 (yes, Year 0) - this is the
204* first day of a 400-year long "era", right after additional day of
205* leap year. This adjustment is required only for date calculation, so
206* instead of modifying time_t value (which would require 64-bit
207* operations to work correctly) it's enough to adjust the calculated
208* number of days since epoch.
209*/
210#define EPOCH_ADJUSTMENT_DAYS 719468L211
212/* Year to which the adjustment was made. */
213#define ADJUSTED_EPOCH_YEAR 0214
215/* 1st March of year 0 is Wednesday. */
216#define ADJUSTED_EPOCH_WDAY 3217
218/*
219* There are 24 leap years in 100-year periods. ((100 - 24) * 365 + 24 *
220* 366).
221*/
222#define DAYS_PER_CENTURY 36524L223
224/* There is one leap year every 4 years. */
225#define DAYS_PER_4_YEARS (3 * 365 + 366)226
227/* Number of days in a non-leap year. */
228#define DAYS_PER_YEAR 365229
230/* Number of days in January. */
231#define DAYS_IN_JANUARY 31232
233/* Number of days in non-leap February. */
234#define DAYS_IN_FEBRUARY 28235
236/* Number of years per era. */
237#define YEARS_PER_ERA 400238
239/* Various constants. */
240#define SECSPERMIN 60L241#define MINSPERHOUR 60L242#define HOURSPERDAY 24L243#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR)244#define SECSPERDAY (SECSPERHOUR * HOURSPERDAY)245#define YEAR_BASE 1900246#define DAYSPERWEEK 7247#define MONSPERYEAR 12248
249/*
250* This form of _ISLEAP doesn't adjust the year.
251*/
252#define _ISLEAP2(y) \253(((y) % 4) == 0 && \254(((y) % 100) != 0 || ((y) % 400) == 0))255
256/*
257* Convert UNIX epoch to values in "res".
258*/
259static void260khttp_gmtime_r(int64_t lcltime, struct tm64 *res)261{
262int64_t days, rem, era, weekday, year;263uint64_t erayear, yearday, month, day, eraday;264
265memset(res, 0, sizeof(struct tm64));266
267days = lcltime / SECSPERDAY + EPOCH_ADJUSTMENT_DAYS;268rem = lcltime % SECSPERDAY;269
270if (rem < 0) {271rem += SECSPERDAY;272--days;273}274
275/* Compute hour, min, and sec. */276
277res->tm_hour = (int64_t)(rem / SECSPERHOUR);278rem %= SECSPERHOUR;279res->tm_min = (int64_t)(rem / SECSPERMIN);280res->tm_sec = (int64_t)(rem % SECSPERMIN);281
282/* Compute day of week. */283
284if ((weekday = ((ADJUSTED_EPOCH_WDAY + days) % DAYSPERWEEK)) < 0)285weekday += DAYSPERWEEK;286
287res->tm_wday = weekday;288
289/*290* Compute year, month, day & day of year.
291* For description of this algorithm see
292* http://howardhinnant.github.io/date_algorithms.html#civil_from_days
293*/
294
295era = (days >= 0 ? days : days - (DAYS_PER_ERA - 1)) / DAYS_PER_ERA;296
297/* [0, 146096] */298eraday = days - era * DAYS_PER_ERA;299
300/* [0, 399] */301erayear = (eraday - eraday / (DAYS_PER_4_YEARS - 1) +302eraday / DAYS_PER_CENTURY - eraday /303(DAYS_PER_ERA - 1)) / 365;304
305/* [0, 365] */306yearday = eraday - (DAYS_PER_YEAR * erayear +307erayear / 4 - erayear / 100);308
309/* [0, 11] */310month = (5 * yearday + 2) / 153;311
312/* [1, 31] */313day = yearday - (153 * month + 2) / 5 + 1;314
315month += month < 10 ? 2 : -10;316year = ADJUSTED_EPOCH_YEAR + erayear +317era * YEARS_PER_ERA + (month <= 1);318
319res->tm_yday =320yearday >= DAYS_PER_YEAR -321DAYS_IN_JANUARY - DAYS_IN_FEBRUARY ?322yearday - (DAYS_PER_YEAR -323DAYS_IN_JANUARY - DAYS_IN_FEBRUARY) :324yearday + DAYS_IN_JANUARY +325DAYS_IN_FEBRUARY + _ISLEAP2(erayear);326res->tm_year = year - YEAR_BASE;327res->tm_mon = month;328res->tm_mday = day;329}
330
331char *332khttp_epoch2str(int64_t tt, char *buf, size_t sz)333{
334struct tm64 tm;335char rbuf[MAX_TIME_STRING];336const char *days[7] = {337"Sun",338"Mon",339"Tue",340"Wed",341"Thu",342"Fri",343"Sat"344};345const char *months[12] = {346"Jan",347"Feb",348"Mar",349"Apr",350"May",351"Jun",352"Jul",353"Aug",354"Sep",355"Oct",356"Nov",357"Dec"358};359
360if (buf == NULL || sz == 0)361return NULL;362
363khttp_gmtime_r(tt, &tm);364
365if (snprintf(rbuf, sizeof(rbuf),366"%s, %.2" PRId64 " %s %.4" PRId64 " "367"%.2" PRId64 ":%.2" PRId64 ":%.2" PRId64 " GMT",368days[tm.tm_wday], tm.tm_mday,369months[tm.tm_mon], tm.tm_year + 1900,370tm.tm_hour, tm.tm_min, tm.tm_sec) == -1) {371kutil_warn(NULL, NULL, "snprintf");372return NULL;373}374
375strlcpy(buf, rbuf, sz);376return buf;377}
378
379/*
380* Deprecated interface simply zeroes the time structure if it's
381* negative. The original implementation did not do this (it set the
382* "struct tm" to zero), but the documentation still said otherwise.
383* This uses the original documented behaviour.
384*/
385char *386kutil_epoch2str(int64_t tt, char *buf, size_t sz)387{
388
389return khttp_epoch2str(tt < 0 ? 0 : tt, buf, sz);390}
391
392char *393khttp_epoch2ustr(int64_t tt, char *buf, size_t sz)394{
395char rbuf[MAX_TIME_STRING];396struct tm64 tm;397
398if (buf == NULL || sz == 0)399return NULL;400
401khttp_gmtime_r(tt, &tm);402
403if (snprintf(rbuf, sizeof(rbuf),404"%.4" PRId64 "-%.2" PRId64 "-%.2" PRId64405"T%.2" PRId64 ":%.2" PRId64 ":%.2" PRId64 "Z",406tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,407tm.tm_hour, tm.tm_min, tm.tm_sec) == -1) {408kutil_warn(NULL, NULL, "snprintf");409return NULL;410}411
412strlcpy(buf, rbuf, sz);413return buf;414}
415
416/*
417* Deprecated.
418* See kutil_epoch2str() for behaviour notes.
419*/
420char *421kutil_epoch2utcstr(int64_t tt, char *buf, size_t sz)422{
423
424return khttp_epoch2ustr(tt < 0 ? 0 : tt, buf, sz);425}
426
427void
428khttp_epoch2datetime(int64_t tt, int64_t *tm_sec, int64_t *tm_min,429int64_t *tm_hour, int64_t *tm_mday, int64_t *tm_mon,430int64_t *tm_year, int64_t *tm_wday, int64_t *tm_yday)431{
432struct tm64 tm;433
434khttp_gmtime_r(tt, &tm);435
436if (tm_sec != NULL)437*tm_sec = tm.tm_sec;438if (tm_min != NULL)439*tm_min = tm.tm_min;440if (tm_hour != NULL)441*tm_hour = tm.tm_hour;442if (tm_mday != NULL)443*tm_mday = tm.tm_mday;444if (tm_mon != NULL)445*tm_mon = tm.tm_mon + 1;446if (tm_year != NULL)447*tm_year = tm.tm_year + 1900;448if (tm_wday != NULL)449*tm_wday = tm.tm_wday;450if (tm_yday != NULL)451*tm_yday = tm.tm_yday;452}
453
454int
455khttp_epoch2tms(int64_t tt, int *tm_sec, int *tm_min,456int *tm_hour, int *tm_mday, int *tm_mon,457int *tm_year, int *tm_wday, int *tm_yday)458{
459struct tm64 tm;460
461khttp_gmtime_r(tt, &tm);462
463if (tm.tm_year > INT_MAX || tm.tm_year < -INT_MAX)464return 0;465if (tm_sec != NULL)466*tm_sec = tm.tm_sec;467if (tm_min != NULL)468*tm_min = tm.tm_min;469if (tm_hour != NULL)470*tm_hour = tm.tm_hour;471if (tm_mday != NULL)472*tm_mday = tm.tm_mday;473if (tm_mon != NULL)474*tm_mon = tm.tm_mon;475if (tm_year != NULL)476*tm_year = tm.tm_year;477if (tm_wday != NULL)478*tm_wday = tm.tm_wday;479if (tm_yday != NULL)480*tm_yday = tm.tm_yday;481
482return 1;483}
484
485/*
486* Deprecated.
487* This has a bad corner case where gmtime() returns NULL and we just
488* assume the epoch---this wasn't present in the earlier version of this
489* because it was using a hand-rolled gmtime() that didn't fail. This
490* is suitably unlikely that it's ok, as it's deprecated.
491*/
492void
493kutil_epoch2tmvals(int64_t tt, int *tm_sec, int *tm_min,494int *tm_hour, int *tm_mday, int *tm_mon,495int *tm_year, int *tm_wday, int *tm_yday)496{
497
498khttp_epoch2tms(tt < 0 ? 0 : tt, tm_sec, tm_min,499tm_hour, tm_mday, tm_mon, tm_year, tm_wday, tm_yday);500}
501
502int
503khttp_datetime2epoch(int64_t *res, int64_t day, int64_t mon,504int64_t year, int64_t hour, int64_t min, int64_t sec)505{
506struct tm64 tm;507int64_t val;508
509if (res == NULL)510res = &val;511
512memset(&tm, 0, sizeof(struct tm64));513tm.tm_sec = sec;514tm.tm_min = min;515tm.tm_hour = hour;516tm.tm_mday = day;517tm.tm_mon = mon - 1;518tm.tm_year = year - 1900;519return khttp_mktime(res, &tm);520}
521
522int
523khttp_date2epoch(int64_t *res, int64_t day, int64_t mon, int64_t year)524{
525
526return khttp_datetime2epoch(res, day, mon, year, 0, 0, 0);527}
528
529/*
530* Deprecated interface.
531*/
532int64_t
533kutil_date2epoch(int64_t day, int64_t mon, int64_t year)534{
535int64_t res;536
537if (!khttp_date2epoch(&res, day, mon, year))538return -1;539
540return res;541}
542
543/*
544* Deprecated interface.
545*/
546int
547kutil_datetime_check(int64_t day, int64_t mon, int64_t year,548int64_t hour, int64_t min, int64_t sec)549{
550
551return khttp_datetime2epoch552(NULL, day, mon, year, hour, min, sec);553}
554
555/*
556* Deprecated interface.
557*/
558int
559kutil_date_check(int64_t day, int64_t mon, int64_t year)560{
561
562return khttp_date2epoch(NULL, day, mon, year);563}
564
565/*
566* Deprecated interface.
567*/
568int64_t
569kutil_datetime2epoch(int64_t day, int64_t mon, int64_t year,570int64_t hour, int64_t min, int64_t sec)571{
572int64_t res;573
574if (!khttp_datetime2epoch575(&res, day, mon, year, hour, min, sec))576return -1;577
578return res;579}
580