blob: 7c3f231f236efa3b07bb2328c71c735094ef3093 [file] [log] [blame]
/*
* Copyright (C) Tildeslash Ltd. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
*
* You must obey the GNU Affero General Public License in all respects
* for all of the code used other than OpenSSL.
*/
#include "Config.h"
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <ctype.h>
#include <stdarg.h>
#include <unistd.h>
#include "Str.h"
#include "system/System.h"
#include "system/Time.h"
/**
* Implementation of the Time interface
*
* @see http://www.mmonit.com/
* @file
*/
/* ----------------------------------------------------------- Definitions */
#define CTIME "%a %b %d %H:%M:%S %z %Y"
#define RFC822 "%a, %d %b %Y %H:%M:%S %z"
#define RFC1123 "%a, %d %b %Y %H:%M:%S GMT"
#define TEST_RANGE(v, f, t) \
do { \
if (v < f || v > t) \
THROW(AssertException, "#v is outside the range (%d..%d)", f, t); \
} while (0)
static const char days[] = "SunMonTueWedThuFriSat";
static const char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
#define MONTHS_LEN 36
/* --------------------------------------------------------------- Private */
static time_t parseDate(const char *date) {
#define YYCTYPE char
#define YYCURSOR date
#define YYLIMIT end
#define YYMARKER m
#define YYFILL(n) ((void)0)
const char *t;
const char *m;
struct tm time = {0};
const char *end = date + strlen(date);
time.tm_mon = -1;
time.tm_year = -1;
time.tm_mday = -1;
time.tm_isdst = -1;
for (;;) {
if (YYCURSOR >= YYLIMIT) {
if (time.tm_mon== -1 || time.tm_year== -1 || time.tm_mday== -1)
return -1;
return mktime(&time);
}
t = YYCURSOR;
{
YYCTYPE yych;
unsigned int yyaccept = 0;
if ((YYLIMIT - YYCURSOR) < 8) YYFILL(8);
yych = *YYCURSOR;
switch (yych) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': goto yy11;
case 'A':
case 'a': goto yy6;
case 'D':
case 'd': goto yy10;
case 'F':
case 'f': goto yy4;
case 'J':
case 'j': goto yy2;
case 'M':
case 'm': goto yy5;
case 'N':
case 'n': goto yy9;
case 'O':
case 'o': goto yy8;
case 'S':
case 's': goto yy7;
default: goto yy12;
}
yy2:
yyaccept = 0;
yych = *(YYMARKER = ++YYCURSOR);
if (yych <= 'U') {
if (yych == 'A') goto yy53;
if (yych >= 'U') goto yy52;
} else {
if (yych <= 'a') {
if (yych >= 'a') goto yy53;
} else {
if (yych == 'u') goto yy52;
}
}
yy3:
{
continue;
}
yy4:
yyaccept = 0;
yych = *(YYMARKER = ++YYCURSOR);
if (yych == 'E') goto yy49;
if (yych == 'e') goto yy49;
goto yy3;
yy5:
yyaccept = 0;
yych = *(YYMARKER = ++YYCURSOR);
if (yych == 'A') goto yy44;
if (yych == 'a') goto yy44;
goto yy3;
yy6:
yyaccept = 0;
yych = *(YYMARKER = ++YYCURSOR);
if (yych <= 'U') {
if (yych == 'P') goto yy39;
if (yych <= 'T') goto yy3;
goto yy38;
} else {
if (yych <= 'p') {
if (yych <= 'o') goto yy3;
goto yy39;
} else {
if (yych == 'u') goto yy38;
goto yy3;
}
}
yy7:
yyaccept = 0;
yych = *(YYMARKER = ++YYCURSOR);
if (yych == 'E') goto yy35;
if (yych == 'e') goto yy35;
goto yy3;
yy8:
yyaccept = 0;
yych = *(YYMARKER = ++YYCURSOR);
if (yych == 'C') goto yy32;
if (yych == 'c') goto yy32;
goto yy3;
yy9:
yyaccept = 0;
yych = *(YYMARKER = ++YYCURSOR);
if (yych == 'O') goto yy29;
if (yych == 'o') goto yy29;
goto yy3;
yy10:
yyaccept = 0;
yych = *(YYMARKER = ++YYCURSOR);
if (yych == 'E') goto yy26;
if (yych == 'e') goto yy26;
goto yy3;
yy11:
yych = *++YYCURSOR;
if (yych <= '/') goto yy3;
if (yych <= '9') goto yy13;
goto yy3;
yy12:
yych = *++YYCURSOR;
goto yy3;
yy13:
yyaccept = 1;
yych = *(YYMARKER = ++YYCURSOR);
if (yych <= '/') goto yy14;
if (yych <= '9') goto yy15;
if (yych <= ':') goto yy17;
yy14:
{
if (sscanf(t, "%d", &time.tm_mday) != 1)
time.tm_mday = -1;
continue;
}
yy15:
yych = *++YYCURSOR;
if (yych <= '/') goto yy16;
if (yych <= '9') goto yy24;
yy16:
YYCURSOR = YYMARKER;
if (yyaccept <= 0) {
goto yy3;
} else {
goto yy14;
}
yy17:
yych = *++YYCURSOR;
if (yych <= '/') goto yy16;
if (yych >= ':') goto yy16;
yych = *++YYCURSOR;
if (yych <= '/') goto yy16;
if (yych >= ':') goto yy16;
yych = *++YYCURSOR;
if (yych != ':') goto yy16;
yych = *++YYCURSOR;
if (yych <= '/') goto yy16;
if (yych >= ':') goto yy16;
yych = *++YYCURSOR;
if (yych <= '/') goto yy16;
if (yych >= ':') goto yy16;
++YYCURSOR;
{
sscanf(t, "%d:%d:%d", &time.tm_hour, &time.tm_min, &time.tm_sec);
continue;
}
yy24:
++YYCURSOR;
{
if (sscanf(t, "%d", &time.tm_year) == 1)
time.tm_year -=1900;
else
time.tm_year = -1;
continue;
}
yy26:
yych = *++YYCURSOR;
if (yych == 'C') goto yy27;
if (yych != 'c') goto yy16;
yy27:
++YYCURSOR;
{
time.tm_mon = 11;
continue;
}
yy29:
yych = *++YYCURSOR;
if (yych == 'V') goto yy30;
if (yych != 'v') goto yy16;
yy30:
++YYCURSOR;
{
time.tm_mon = 10;
continue;
}
yy32:
yych = *++YYCURSOR;
if (yych == 'T') goto yy33;
if (yych != 't') goto yy16;
yy33:
++YYCURSOR;
{
time.tm_mon = 9;
continue;
}
yy35:
yych = *++YYCURSOR;
if (yych == 'P') goto yy36;
if (yych != 'p') goto yy16;
yy36:
++YYCURSOR;
{
time.tm_mon = 8;
continue;
}
yy38:
yych = *++YYCURSOR;
if (yych == 'G') goto yy42;
if (yych == 'g') goto yy42;
goto yy16;
yy39:
yych = *++YYCURSOR;
if (yych == 'R') goto yy40;
if (yych != 'r') goto yy16;
yy40:
++YYCURSOR;
{
time.tm_mon = 3;
continue;
}
yy42:
++YYCURSOR;
{
time.tm_mon = 7;
continue;
}
yy44:
yych = *++YYCURSOR;
if (yych <= 'Y') {
if (yych == 'R') goto yy45;
if (yych <= 'X') goto yy16;
goto yy47;
} else {
if (yych <= 'r') {
if (yych <= 'q') goto yy16;
} else {
if (yych == 'y') goto yy47;
goto yy16;
}
}
yy45:
++YYCURSOR;
{
time.tm_mon = 2;
continue;
}
yy47:
++YYCURSOR;
{
time.tm_mon = 4;
continue;
}
yy49:
yych = *++YYCURSOR;
if (yych == 'B') goto yy50;
if (yych != 'b') goto yy16;
yy50:
++YYCURSOR;
{
time.tm_mon = 1;
continue;
}
yy52:
yych = *++YYCURSOR;
if (yych <= 'N') {
if (yych == 'L') goto yy58;
if (yych <= 'M') goto yy16;
goto yy56;
} else {
if (yych <= 'l') {
if (yych <= 'k') goto yy16;
goto yy58;
} else {
if (yych == 'n') goto yy56;
goto yy16;
}
}
yy53:
yych = *++YYCURSOR;
if (yych == 'N') goto yy54;
if (yych != 'n') goto yy16;
yy54:
++YYCURSOR;
{
time.tm_mon = 0;
continue;
}
yy56:
++YYCURSOR;
{
time.tm_mon = 5;
continue;
}
yy58:
++YYCURSOR;
{
time.tm_mon = 6;
continue;
}
}
}
return -1;
}
/* ----------------------------------------------------------------- Class */
time_t Time_build(int year, int month, int day, int hour, int min, int sec) {
struct tm tm = {.tm_isdst = -1};
TEST_RANGE(year, 1970, 2037);
TEST_RANGE(month, 1, 12);
TEST_RANGE(day, 1, 31);
TEST_RANGE(hour, 0, 23);
TEST_RANGE(min, 0, 59);
TEST_RANGE(sec, 0, 61);
tm.tm_year = (year - 1900);
tm.tm_mon = (month - 1);
tm.tm_mday = day;
tm.tm_hour = hour;
tm.tm_min = min;
tm.tm_sec = sec;
return mktime(&tm);
}
time_t Time_parse(const char *date) {
if (STR_DEF(date))
return parseDate(date);
return -1;
}
time_t Time_now(void) {
struct timeval t;
if (gettimeofday(&t, NULL) != 0)
THROW(AssertException, "%s", System_getLastError());
return t.tv_sec;
}
long long int Time_milli(void) {
struct timeval t;
if (gettimeofday(&t, NULL) != 0)
THROW(AssertException, "%s", System_getLastError());
return (long long int)t.tv_sec * 1000 + (long long int)t.tv_usec / 1000;
}
time_t Time_gmt(time_t localtime) {
struct tm r;
gmtime_r(&localtime, &r);
return mktime(&r);
}
int Time_seconds(time_t time) {
struct tm tm;
localtime_r(&time, &tm);
return tm.tm_sec;
}
int Time_minutes(time_t time) {
struct tm tm;
localtime_r(&time, &tm);
return tm.tm_min;
}
int Time_hour(time_t time) {
struct tm tm;
localtime_r(&time, &tm);
return tm.tm_hour;
}
int Time_weekday(time_t time) {
struct tm tm;
localtime_r(&time, &tm);
return tm.tm_wday;
}
int Time_day(time_t time) {
struct tm tm;
localtime_r(&time, &tm);
return tm.tm_mday;
}
int Time_month(time_t time) {
struct tm tm;
localtime_r(&time, &tm);
return (tm.tm_mon + 1);
}
int Time_year(time_t time) {
struct tm tm;
localtime_r(&time, &tm);
return (tm.tm_year + 1900);
}
time_t Time_add(time_t time, int years, int months, int days) {
struct tm tm;
localtime_r(&time, &tm);
tm.tm_year += years;
tm.tm_mon += months;
tm.tm_mday += days;
tm.tm_isdst = -1;
return mktime(&tm);
}
int Time_daysBetween(time_t to, time_t from) {
double t = difftime(to, from);
if (t < 0) t *= -1;
return ((t + (86400L/2))/86400L);
}
char *Time_string(time_t time, char *result) {
#define i2a(i) (x[0]=(i/10)+'0', x[1]=(i%10)+'0')
if (result) {
char x[2];
struct tm ts;
/* This implementation needs to be fast and is around 50%
faster than strftime */
localtime_r((const time_t *)&time, &ts);
memcpy(result, "aaa, xx aaa xxxx xx:xx:xx\0", 26);
/* 0 5 8 1214 17 20 2326 */
memcpy(result, days+3*ts.tm_wday, 3);
i2a(ts.tm_mday);
result[5] = x[0];
result[6] = x[1];
memcpy(result + 8, months+3*ts.tm_mon, 3);
i2a((ts.tm_year+1900)/100);
result[12] = x[0];
result[13] = x[1];
i2a((ts.tm_year+1900)%100);
result[14] = x[0];
result[15] = x[1];
i2a(ts.tm_hour);
result[17] = x[0];
result[18] = x[1];
i2a(ts.tm_min);
result[20] = x[0];
result[21] = x[1];
i2a(ts.tm_sec);
result[23] = x[0];
result[24] = x[1];
}
return result;
}
char *Time_gmtstring(time_t time, char *result) {
if (result) {
char x[2];
struct tm ts;
/* This implementation needs to be fast and is around 50%
faster than strftime */
gmtime_r(&time, &ts);
memcpy(result, "aaa, xx aaa xxxx xx:xx:xx GMT\0", 30);
/* 0 5 8 1214 17 20 23 29 */
memcpy(result, days+3*ts.tm_wday, 3);
i2a(ts.tm_mday);
result[5] = x[0];
result[6] = x[1];
memcpy(result + 8, months+3*ts.tm_mon, 3);
i2a((ts.tm_year+1900)/100);
result[12] = x[0];
result[13] = x[1];
i2a((ts.tm_year+1900)%100);
result[14] = x[0];
result[15] = x[1];
i2a(ts.tm_hour);
result[17] = x[0];
result[18] = x[1];
i2a(ts.tm_min);
result[20] = x[0];
result[21] = x[1];
i2a(ts.tm_sec);
result[23] = x[0];
result[24] = x[1];
}
return result;
}
char *Time_fmt(char *result, int size, const char *format, time_t time) {
struct tm tm;
assert(result);
assert(format);
localtime_r((const time_t *)&time, &tm);
if (strftime(result, size, format, &tm) == 0)
*result = 0;
return result;
}
char *Time_uptime(time_t sec, char *result) {
// Write max 24 bytes to result
if (result) {
int n = 0;
time_t r = 0;
result[0] = 0;
if (sec > 0) {
if ((r = sec/86400) > 0) {
n = snprintf(result, 24, "%ldd", r);
sec -= r * 86400;
}
if((r = sec/3600) > 0) {
n += snprintf(result + n, (24 - n), "%s%ldh", n ? ", " : "", r);
sec -= r * 3600;
}
r = sec/60;
snprintf(result + n, (24 - n), "%s%ldm", n ? ", " : "", r);
}
}
return result;
}
/*
cron string is on format "minute hour day month wday"
where fields may have a numeric type, an asterix, a
sequence of numbers or a range
*/
int Time_incron(const char *cron, time_t time) {
assert(cron);
#undef YYCURSOR
#undef YYLIMIT
#undef YYMARKER
#define YYCURSOR cron
#define YYLIMIT end
#define YYMARKER m
#define YYTOKEN t
const char *m;
const char *t;
const char *end = cron + strlen(cron);
int n = 0;
int found = 0;
int fields[] = {Time_minutes(time), Time_hour(time), Time_day(time), Time_month(time), Time_weekday(time)};
parse:
if (YYCURSOR >= YYLIMIT)
return found == 5;
YYTOKEN = YYCURSOR;
{
YYCTYPE yych;
static const unsigned char yybm[] = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
128, 128, 128, 128, 128, 128, 128, 128,
128, 128, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
};
if ((YYLIMIT - YYCURSOR) < 3) YYFILL(3);
yych = *YYCURSOR;
if (yych <= ' ') {
if (yych <= '\f') {
if (yych <= 0x08) goto yy70;
if (yych >= '\v') goto yy70;
} else {
if (yych <= '\r') goto yy62;
if (yych <= 0x1F) goto yy70;
}
} else {
if (yych <= '+') {
if (yych == '*') goto yy64;
goto yy70;
} else {
if (yych <= ',') goto yy66;
if (yych <= '/') goto yy70;
if (yych <= '9') goto yy68;
goto yy70;
}
}
yy62:
++YYCURSOR;
{
goto parse;
}
yy64:
++YYCURSOR;
{
n++;
found++;
goto parse;
}
yy66:
++YYCURSOR;
{
n--; // backtrack on field advance
assert(n < 5 && n >= 0);
goto parse;
}
yy68:
yych = *(YYMARKER = ++YYCURSOR);
goto yy73;
yy69:
{
int v = Str_parseInt(YYTOKEN);
if (fields[n] == v)
found++;
n++;
goto parse;
}
yy70:
++YYCURSOR;
{
return false;
}
yy72:
YYMARKER = ++YYCURSOR;
if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2);
yych = *YYCURSOR;
yy73:
if (yybm[0+yych] & 128) {
goto yy72;
}
if (yych != '-') goto yy69;
yych = *++YYCURSOR;
if (yych <= '/') goto yy75;
if (yych <= '9') goto yy76;
yy75:
YYCURSOR = YYMARKER;
goto yy69;
yy76:
++YYCURSOR;
if (YYLIMIT <= YYCURSOR) YYFILL(1);
yych = *YYCURSOR;
if (yych <= '/') goto yy78;
if (yych <= '9') goto yy76;
yy78:
{
int from = Str_parseInt(YYTOKEN);
int to = Str_parseInt(strchr(YYTOKEN, '-') + 1);
if ((fields[n] <= to) && (fields[n] >= from))
found++;
n++;
goto parse;
}
}
return found == 5;
}
void Time_usleep(long u) {
#ifdef NETBSD
// usleep is broken on NetBSD (at least in version 5.1)
struct timespec t = {u / 1000000, (u % 1000000) * 1000};
nanosleep(&t, NULL);
#else
usleep((useconds_t)u);
#endif
}