/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1985-2012 AT&T Intellectual Property          *
*          Copyright (c) 2020-2024 Contributors to ksh 93u+m           *
*                      and is licensed under the                       *
*                 Eclipse Public License, Version 2.0                  *
*                                                                      *
*                A copy of the License is available at                 *
*      https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html      *
*         (with md5 checksum 84283fa8859daf213bdda5a9f8d1be1d)         *
*                                                                      *
*               Glenn Fowler <glenn.s.fowler@gmail.com>                *
*                    David Korn <dgkorn@gmail.com>                     *
*                     Phong Vo <phongvo@gmail.com>                     *
*                  Martijn Dekker <martijn@inlv.org>                   *
*            Johnothan King <johnothanking@protonmail.com>             *
*               K. Eugene Carlson <kvngncrlsn@gmail.com>               *
*                                                                      *
***********************************************************************/
/*
 * Glenn Fowler
 * AT&T Research
 *
 * time conversion support
 */

#include <tm.h>
#include <ctype.h>
#include <namval.h>

#include "FEATURE/tmlib"

#ifndef tzname
#	if defined(__DYNAMIC__)
#		undef	_dat_tzname
#		define	tzname		__DYNAMIC__(tzname)
#	else
#		if !_dat_tzname
#			if _dat__tzname
#				undef	_dat_tzname
#				define _dat_tzname	1
#				define tzname		_tzname
#			endif
#		endif
#	endif
#	if _dat_tzname && !defined(tzname)
		extern char*		tzname[];
#	endif
#endif

#define TM_type		(-1)

static const Namval_t		options[] =
{
	"adjust",	TM_ADJUST,
	"format",	TM_DEFAULT,
	"leap",		TM_LEAP,
	"subsecond",	TM_SUBSECOND,
	"type",		TM_type,
	"utc",		TM_UTC,
	0,		0
};

/*
 * 2007-03-19 move tm_info from _tm_info_ to (*_tm_infop_)
 *	      to allow future Tm_info_t growth
 */

static Tm_info_t	_tm_info_ = { 0 };
Tm_info_t*		_tm_infop_ = &_tm_info_;

static char*		tz_abbr;

#if _tzset_environ

static char	TZ[256];
static char*	TE[2];

struct tm*
_tm_localtime(const time_t* t)
{
	struct tm*	r;
	char*		e = NULL;
	char**		v = environ;

	if (TZ[0])
	{
		if (!environ || !*environ)
			environ = TE;
		else
			e = environ[0];
		environ[0] = TZ;
	}
	r = localtime(t);
	if (TZ[0])
	{
		if (environ != v)
			environ = v;
		else
			environ[0] = e;
	}
	return r;
}

#endif

/*
 * return minutes west of GMT for local time clock
 *
 * isdst will point to non-zero if DST is in effect
 * this routine also kicks in the local initialization
 */

static int
tzwest(time_t* clock, int* isdst)
{
	struct tm*	tp;
	int		n;
	int		m;
	int		h;
	time_t		epoch;

	/*
	 * convert to GMT assuming local time
	 */

	if (!(tp = gmtime(clock)))
	{
		/*
		 * some systems return 0 for negative time_t
		 */

		epoch = 0;
		clock = &epoch;
		tp = gmtime(clock);
	}
	n = tp->tm_yday;
	h = tp->tm_hour;
	m = tp->tm_min;

	/*
	 * tmlocaltime() handles DST and GMT offset
	 */

	tp = tmlocaltime(clock);
#if _mem_tm_zone_tm
	if (tp->tm_zone && !tz_abbr)
		tz_abbr = strdup(tp->tm_zone);
#endif
	if (n = tp->tm_yday - n)
	{
		if (n > 1)
			n = -1;
		else if (n < -1)
			n = 1;
	}
	*isdst = tp->tm_isdst;
	return (h - tp->tm_hour - n * 24) * 60 + m - tp->tm_min;
}

/*
 * stropt() option handler
 */

static int
tmopt(void* a, const void* p, int n, const char* v)
{
	Tm_zone_t*	zp;

	NoP(a);
	if (p)
		switch (((Namval_t*)p)->value)
		{
		case TM_DEFAULT:
			tm_info.deformat = (n && (n = strlen(v)) > 0 && (n < 2 || v[n-2] != '%' || v[n-1] != '?')) ? strdup(v) : tm_info.format[TM_DEFAULT];
			break;
		case TM_type:
			tm_info.local->type = (n && *v) ? ((zp = tmtype(v, NULL)) ? zp->type : strdup(v)) : 0;
			break;
		default:
			if (n)
				tm_info.flags |= ((Namval_t*)p)->value;
			else
				tm_info.flags &= ~((Namval_t*)p)->value;
			break;
		}
	return 0;
}

/*
 * initialize the local timezone
 */

static void
tmlocal(time_t now)
{
	Tm_zone_t*		zp;
	int			n;
	char*			s;
	char*			e = NULL;
	int			i;
	int			m;
	int			isdst;
	char*			t;
	struct tm*		tp;
	char			buf[16];

	static Tm_zone_t	local;

	local.standard = 0;
	local.daylight = 0;

#if _tzset_environ
	{
		char**	v = environ;

		if (s = getenv("TZ"))
		{
			sfsprintf(TZ, sizeof(TZ), "TZ=%s", s);
			if (!environ || !*environ)
				environ = TE;
			else
				e = environ[0];
			environ[0] = TZ;
		}
		else
		{
			TZ[0] = 0;
			e = 0;
		}
#endif
#if _lib_tzset
		tzset();
#endif
#if _tzset_environ
		if (environ != v)
			environ = v;
		else if (e)
			environ[0] = e;
	}
#endif
	tmlocale();

	/*
	 * tm_info.local
	 */

	tm_info.zone = tm_info.local = &local;
	n = tzwest(&now, &isdst);

	/*
	 * compute local DST offset by roaming
	 * through the last 12 months until tzwest() changes
	 */

	for (i = 0; i < 12; i++)
	{
		now -= 31 * 24 * 60 * 60;
		if ((m = tzwest(&now, &isdst)) != n)
		{
			if (!isdst)
			{
				isdst = n;
				n = m;
				m = isdst;
			}
			m -= n;
			break;
		}
	}
	local.west = n;
	local.dst = m;

	/*
	 * now get the time zone names
	 */

	if (tz_abbr)
	{
		if (!isdst)
			local.standard = strdup(tz_abbr);
		else
			local.daylight = strdup(tz_abbr);
	}
#if _dat_tzname
	if (tzname[0])
	{
		/*
		 * POSIX
		 */

		if (!local.standard)
			local.standard = strdup(tzname[0]);
		if (!local.daylight)
			local.daylight = strdup(tzname[1]);
	}
	else
#endif
	if ((s = getenv("TZNAME")) && *s && (s = strdup(s)))
	{
		/*
		 * BSD
		 */

		if (!local.standard)
			local.standard = s;
		if (!local.daylight)
		{
			if (s = strchr(s, ','))
				*s++ = 0;
			else
				s = "";
			local.daylight = s;
		}
	}
	else
	{
		/*
		 * tm_data.zone table lookup
		 */

		t = 0;
		for (zp = tm_data.zone; zp->standard; zp++)
		{
			if (zp->type)
				t = zp->type;
			if (zp->west == n && zp->dst == m)
			{
				local.type = t;
				if (!local.standard)
					local.standard = zp->standard;
				if (!(s = zp->daylight))
				{
					e = (s = buf) + sizeof(buf);
					s = tmpoff(s, e - s, zp->standard, 0, 0);
					if (s < e - 1)
					{
						*s++ = ' ';
						tmpoff(s, e - s, tm_info.format[TM_DT], m, TM_DST);
					}
					s = strdup(buf);
				}
				if (!local.daylight)
					local.daylight = s;
				break;
			}
		}
		if (!zp->standard)
		{
			/*
			 * not in the table
			 */

			e = (s = buf) + sizeof(buf);
			s = tmpoff(s, e - s, tm_info.format[TM_UT], n, 0);
			if (!local.standard)
				local.standard = strdup(buf);
			if (s < e - 1)
			{
				*s++ = ' ';
				tmpoff(s, e - s, tm_info.format[TM_UT], m, TM_DST);
				if (!local.daylight)
					local.daylight = strdup(buf);
			}
		}
	}
	if (!*local.standard && !local.west && !local.dst && (s = getenv("TZ")))
	{
		if ((zp = tmzone(s, &t, NULL, NULL)) && !*t)
		{
			local.standard = strdup(zp->standard);
			if (zp->daylight)
				local.daylight = strdup(zp->daylight);
			local.west = zp->west;
			local.dst = zp->dst;
		}
		else
			local.standard = strdup(s);
		if (!local.standard)
			local.standard = "";
		if (!local.daylight)
			local.daylight = "";
	}

	/*
	 * set the options
	 */

	stropt(getenv("TM_OPTIONS"), options, sizeof(*options), tmopt, NULL);

	/*
	 * the time zone type is probably related to the locale
	 */

	if (!local.type)
	{
		s = local.standard;
		t = 0;
		for (zp = tm_data.zone; zp->standard; zp++)
		{
			if (zp->type)
				t = zp->type;
			if (tmword(s, NULL, zp->standard, NULL, 0))
			{
				local.type = t;
				break;
			}
		}
	}

	/*
	 * tm_info.flags
	 */

	if (!(tm_info.flags & TM_ADJUST))
	{
		now = (time_t)78811200;		/* Jun 30 1972 23:59:60 */
		tp = tmlocaltime(&now);
		if (tp->tm_sec != 60)
			tm_info.flags |= TM_ADJUST;
	}
	if (!(tm_info.flags & TM_UTC))
	{
		s = local.standard;
		zp = tm_data.zone;
		if (local.daylight)
			zp++;
		for (; !zp->type && zp->standard; zp++)
			if (tmword(s, NULL, zp->standard, NULL, 0))
			{
				tm_info.flags |= TM_UTC;
				break;
			}
	}
}

/*
 * initialize tm data
 */

void
tminit(Tm_zone_t* zp, time_t now, const char newzone)
{
	static uint32_t		serial = ~(uint32_t)0;

	tz_abbr = 0;
	if (serial != ast.env_serial)
	{
		serial = ast.env_serial;
		if (tm_info.local)
		{
			memset(tm_info.local, 0, sizeof(*tm_info.local));
			tm_info.local = 0;
		}
	}
	if (!tm_info.local || newzone)
		tmlocal(now);
	if (!zp || newzone)
		zp = tm_info.local;
	tm_info.zone = zp;
}
