/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */

#include "test-lib.h"
#include "time-util.h"

#include <time.h>

static void test_timeval_cmp(void)
{
	static const struct {
		struct timeval tv1, tv2;
		int output;
	} tests[] = {
		{
			.tv1 = { 0, 0 },
			.tv2 = { 0, 0 },
			.output = 0,
		}, {
			.tv1 = { INT_MAX, 999999 },
			.tv2 = { INT_MAX, 999999 },
			.output = 0,
		}, {
			.tv1 = { 0, 0 },
			.tv2 = { 0, 1 },
			.output = -1,
		}, {
			.tv1 = { 0, 0 },
			.tv2 = { 1, 0 },
			.output = -1,
		}, {
			.tv1 = { 0, 999999 },
			.tv2 = { 1, 0 },
			.output = -1,
		}, {
			.tv1 = { 1, 0 },
			.tv2 = { 1, 1 },
			.output = -1,
		}, {
			.tv1 = { -INT_MAX, 0 },
			.tv2 = { INT_MAX, 0 },
			.output = -1,
		}
	};
	unsigned int i;

	test_begin("timeval_cmp()");
	for (i = 0; i < N_ELEMENTS(tests); i++) {
		const struct timeval *tv1 = &tests[i].tv1, *tv2 = &tests[i].tv2;
		int output = tests[i].output;

		test_assert(timeval_cmp(tv1, tv2) == output);
		test_assert(timeval_cmp(tv2, tv1) == -output);
	}
	test_end();
}

static void test_timeval_cmp_margin(void)
{
	static const struct {
		struct timeval tv1, tv2;
		unsigned int margin;
		int output;
	} tests[] = {
		{
			.tv1 = { 0, 0 },
			.tv2 = { 0, 0 },
			.output = 0,
		},{
			.tv1 = { INT_MAX, 999999 },
			.tv2 = { INT_MAX, 999999 },
			.output = 0,

		},{
			.tv1 = { 0, 0 },
			.tv2 = { 0, 1 },
			.output = -1,
		},{
			.tv1 = { 0, 0 },
			.tv2 = { 1, 0 },
			.output = -1,
		},{
			.tv1 = { 0, 999999 },
			.tv2 = { 1, 0 },
			.output = -1,
		},{
			.tv1 = { 1, 0 },
			.tv2 = { 1, 1 },
			.output = -1,
		},{
			.tv1 = { -INT_MAX, 0 },
			.tv2 = { INT_MAX, 0 },
			.output = -1,
		},{
			.tv1 = { 0, 999999 },
			.tv2 = { 1, 0 },
			.margin = 1,
			.output = 0,
		},{
			.tv1 = { 1, 0 },
			.tv2 = { 1, 1 },
			.margin = 1,
			.output = 0,
		},{
			.tv1 = { 0, 999998 },
			.tv2 = { 1, 0 },
			.margin = 1,
			.output = -1,
		},{
			.tv1 = { 1, 0 },
			.tv2 = { 1, 2 },
			.margin = 1,
			.output = -1,
		},{
			.tv1 = { 0, 998000 },
			.tv2 = { 1, 0 },
			.margin = 2000,
			.output = 0,
		},{
			.tv1 = { 1, 0 },
			.tv2 = { 1, 2000 },
			.margin = 2000,
			.output = 0,
		},{
			.tv1 = { 0, 997999 },
			.tv2 = { 1, 0 },
			.margin = 2000,
			.output = -1,
		},{
			.tv1 = { 1, 0 },
			.tv2 = { 1, 2001 },
			.margin = 2000,
			.output = -1,
		},{
			.tv1 = { 0, 1 },
			.tv2 = { 1, 0 },
			.margin = 999999,
			.output = 0,
		},{
			.tv1 = { 1, 0 },
			.tv2 = { 1, 999999 },
			.margin = 999999,
			.output = 0,
		},{
			.tv1 = { 0, 0 },
			.tv2 = { 1, 0 },
			.margin = 999999,
			.output = -1,
		},{
			.tv1 = { 1, 0 },
			.tv2 = { 2, 0 },
			.margin = 999999,
			.output = -1,
		},{
			.tv1 = { 10, 0 },
			.tv2 = { 11, 500000 },
			.margin = 1500000,
			.output = 0,
		},{
			.tv1 = { 8, 500000 },
			.tv2 = { 10, 0 },
			.margin = 1500000,
			.output = 0,
		},{
			.tv1 = { 10, 0 },
			.tv2 = { 11, 500001 },
			.margin = 1500000,
			.output = -1,
		},{
			.tv1 = { 8, 499999 },
			.tv2 = { 10, 0 },
			.margin = 1500000,
			.output = -1,
		},{
			.tv1 = { 1517925358, 999989 },
			.tv2 = { 1517925359, 753 },
			.margin = 2000,
			.output = 0,
		}
	};
	unsigned int i;

	test_begin("timeval_cmp_margin()");
	for (i = 0; i < N_ELEMENTS(tests); i++) {
		const struct timeval *tv1 = &tests[i].tv1, *tv2 = &tests[i].tv2;
		unsigned int margin = tests[i].margin;
		int output = tests[i].output;

		test_assert(timeval_cmp_margin(tv1, tv2, margin) == output);
		test_assert(timeval_cmp_margin(tv2, tv1, margin) == -output);
	}
	test_end();
}

static void test_timeval_diff(void)
{
	static const struct timeval input[] = {
		{ 1, 0 }, { 0, 999999 },
		{ 1, 0 }, { 0, 999001 },
		{ 1, 1 }, { 0, 999001 },
		{ 2, 1 }, { 1, 0 },
		{ INT_MAX, 0 }, { INT_MAX-1, 1 }
	};
	static int output[] = {
		1,
		999,
		1000,
		1000001,
		999999
	};
	unsigned int i;
	long long udiff, mdiff;

	test_begin("timeval_diff_*()");
	for (i = 0; i < N_ELEMENTS(input); i += 2) {
		udiff = timeval_diff_usecs(&input[i], &input[i+1]);
		mdiff = timeval_diff_msecs(&input[i], &input[i+1]);
		test_assert(udiff == output[i/2]);
		test_assert(mdiff == udiff/1000);

		udiff = timeval_diff_usecs(&input[i+1], &input[i]);
		mdiff = timeval_diff_msecs(&input[i+1], &input[i]);
		test_assert(udiff == -output[i/2]);
		test_assert(mdiff == udiff/1000);
	}
	test_end();
}

static void test_time_to_local_day_start(void)
{
	/* Try this around days when DST changes in some of the more popular
	   timezones. If that works, everything else probably works too. */
	const struct tm tests[] = {
		/* Europe winter -> summer */
		{ .tm_year = 2017-1900, .tm_mon = 2, .tm_mday = 26 },
		{ .tm_year = 2017-1900, .tm_mon = 2, .tm_mday = 26,
		  .tm_hour = 23, .tm_min = 59, .tm_sec = 59 },
		/* Europe summer -> winter */
		{ .tm_year = 2017-1900, .tm_mon = 9, .tm_mday = 29 },
		{ .tm_year = 2017-1900, .tm_mon = 9, .tm_mday = 29,
		  .tm_hour = 23, .tm_min = 59, .tm_sec = 59 },
		/* USA winter -> summer */
		{ .tm_year = 2017-1900, .tm_mon = 2, .tm_mday = 12 },
		{ .tm_year = 2017-1900, .tm_mon = 2, .tm_mday = 12,
		  .tm_hour = 23, .tm_min = 59, .tm_sec = 59 },
		/* USA summer -> winter */
		{ .tm_year = 2017-1900, .tm_mon = 10, .tm_mday = 5 },
		{ .tm_year = 2017-1900, .tm_mon = 10, .tm_mday = 5,
		  .tm_hour = 23, .tm_min = 59, .tm_sec = 59 },

		/* (some of) Australia summer -> winter */
		{ .tm_year = 2017-1900, .tm_mon = 3, .tm_mday = 2 },
		{ .tm_year = 2017-1900, .tm_mon = 3, .tm_mday = 2,
		  .tm_hour = 23, .tm_min = 59, .tm_sec = 59 },
		/* (some of) Australia winter -> summer */
		{ .tm_year = 2017-1900, .tm_mon = 9, .tm_mday = 1 },
		{ .tm_year = 2017-1900, .tm_mon = 9, .tm_mday = 1,
		  .tm_hour = 23, .tm_min = 59, .tm_sec = 59 },
	};
	const struct tm *tm;
	struct tm tm_copy;
	time_t t;

	test_begin("time_to_local_day_start()");
	for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
		tm_copy = tests[i];
		tm_copy.tm_isdst = -1;
		t = mktime(&tm_copy);
		test_assert_idx(t != (time_t)-1, i);

		t = time_to_local_day_start(t);
		tm = localtime(&t);
		test_assert_idx(tm->tm_year == tests[i].tm_year &&
				tm->tm_mon == tests[i].tm_mon &&
				tm->tm_mday == tests[i].tm_mday, i);
		test_assert_idx(tm->tm_hour == 0 && tm->tm_min == 0 &&
				tm->tm_sec == 0, i);
	}
	test_end();
}

static void test_timestamp(const char *ts, int idx)
{
	/* %G:%H:%M:%S */
	const char **t = t_strsplit(ts, ":");
	unsigned int len = str_array_length(t);
	test_assert_idx(len == 4, idx);

	/* %G - ISO 8601 year */
	test_assert_idx(strlen(t[0]) == 4, idx);
	unsigned int v = 0;
	test_assert_idx(str_to_uint(t[0], &v) == 0, idx);
	test_assert_idx(1000 <= v, idx);
	test_assert_idx(v <= 3000, idx);

	/* %H - hour from 00 to 23 */
	test_assert_idx(strlen(t[1]) == 2, idx);
	test_assert_idx(str_to_uint(t[1], &v) == 0, idx);
	test_assert_idx(v <= 23, idx);

	/* %M - minute from 00 to 59 */
	test_assert_idx(strlen(t[2]) == 2, idx);
	test_assert_idx(str_to_uint(t[2], &v) == 0, idx);
	test_assert_idx(v <= 59, idx);

	/* %S - second from 00 to 60 */
	test_assert_idx(strlen(t[3]) == 2, idx);
	test_assert_idx(str_to_uint(t[3], &v) == 0, idx);
	test_assert_idx(v <= 60, idx);
}

#define TS_FMT "%G:%H:%M:%S"
static void test_strftime_now(void)
{
	test_begin("t_strftime and variants now");

	time_t now = time(NULL);
	test_timestamp(t_strftime(TS_FMT, gmtime(&now)), 0);
	test_timestamp(t_strfgmtime(TS_FMT, now), 1);
	test_timestamp(t_strflocaltime(TS_FMT, now), 2);

	test_end();
}

#define RFC2822_FMT "%a, %d %b %Y %T"
static void test_strftime_fixed(void)
{
	test_begin("t_strftime and variants fixed timestamp");

	time_t ts = 1481222536;
	const char *exp = "Thu, 08 Dec 2016 18:42:16";
	test_assert(strcmp(t_strftime(RFC2822_FMT, gmtime(&ts)), exp) == 0);
	test_assert(strcmp(t_strfgmtime(RFC2822_FMT, ts), exp) == 0);

	test_end();
}

static void test_micro_nanoseconds(void)
{
	uint64_t secs, usecs, nsecs;

	test_begin("i_microseconds() and i_nanoseconds()");

	secs = time(NULL);
	usecs = i_microseconds();
	nsecs = i_nanoseconds();

	/* Assume max 1 seconds time difference between the calls. That should
	   be more than enough, while still not failing if there are temporary
	   hangs when running in heavily loaded systems. */
	test_assert(usecs/1000000 - secs <= 1);
	test_assert(nsecs/1000 - usecs <= 1000000);

	test_end();
}

static void test_str_to_timeval(void)
{
	struct {
		const char *str;
		time_t tv_sec;
		suseconds_t tv_usec;
	} tests[] = {
		{ "0", 0, 0 },
		{ "0.0", 0, 0 },
		{ "0.000000", 0, 0 },
		{ "0.1", 0, 100000 },
		{ "0.100000", 0, 100000 },
		{ "0.000001", 0, 1 },
		{ "0.100000", 0, 100000 },
		{ "2147483647", 2147483647, 0 },
		{ "2147483647.999999", 2147483647, 999999 },
	};
	const char *test_failures[] = {
		"",
		"0.",
		"0.0000000",
		"1234.-1",
		"1234.x",
		"x",
		"x.100",
	};
	struct timeval tv;

	test_begin("str_to_timeval");
	for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
		test_assert_idx(str_to_timeval(tests[i].str, &tv) == 0, i);
		test_assert_idx(tv.tv_sec == tests[i].tv_sec, i);
		test_assert_idx(tv.tv_usec == tests[i].tv_usec, i);
	}
	for (unsigned int i = 0; i < N_ELEMENTS(test_failures); i++)
		test_assert_idx(str_to_timeval(test_failures[i], &tv) == -1, i);
	test_end();
}

static void test_timeval_from_usecs(void)
{
	test_begin("timeval_from_usecs");
	struct timeval tv0, tv1;

	i_zero(&tv1);
	timeval_from_usecs(&tv0, 0);
	test_assert(timeval_cmp(&tv0, &tv1) == 0);

	tv1.tv_sec = 1;
	tv1.tv_usec = 0;
	timeval_from_usecs(&tv0, 1000000);
	test_assert(timeval_cmp(&tv0, &tv1) == 0);

	tv1.tv_sec = 1;
	tv1.tv_usec = 1;
	timeval_from_usecs(&tv0, 1000001);
	test_assert(timeval_cmp(&tv0, &tv1) == 0);

	tv1.tv_sec = 1;
	tv1.tv_usec = 999999;
	timeval_from_usecs(&tv0, 1999999);
	test_assert(timeval_cmp(&tv0, &tv1) == 0);

	tv1.tv_sec = 0;
	tv1.tv_usec = 999999;
	timeval_from_usecs(&tv0, 999999);
	test_assert(timeval_cmp(&tv0, &tv1) == 0);

	test_end();
}

void test_time_util(void)
{
	test_timeval_cmp();
	test_timeval_cmp_margin();
	test_timeval_diff();
	test_time_to_local_day_start();
	test_strftime_now();
	test_strftime_fixed();
	test_micro_nanoseconds();
	test_str_to_timeval();
	test_timeval_from_usecs();
}
