This post has already been read 3851 times!

This piece of code illustrates how your compiler library handles different settings of the TZ environment variable. It also shows
the problematics arising from time zone conversion localtime/gmtime when daylight saving is currently in effect and the time to be converted would have no daylight time set and vice versa.

In your shell set the environment variable TZ to the value to be tested against, for example for Europe/Berlin set : TZ=CET-1CEST-2,M3.5.0/2,M10.5.0/3

which means:
- Central European Time
- substract one hour to get GMT
- Central European Summer Time daylight saving
- substract two hours to get GMT
- daylight saving starts on the last sunday of march at 02:00
- daylight saving ends on the last sunday of october at 03:00

For a complete description of the available settings see "man newtzset" or similar.

Program usage: timetest [mm [dd [hh]]]

Invoke the program without any parameter to see the compiler's defaults when no tzset() is called, and the results after tzset() was called.

Passing the Month/Day/Hour parameters calculates the given values for the current year.Compare code and output to get a clue. If your daylight saving time is currently in effect, specify a time for which this is not the case, for example January, "timetest 1" (if you are living in the northern hemisphere).

If your daylight saving time is currently not in effect, specify for example June, "timetest 6".
Notice different compiler behaviors when the TZ variable ist not set, for example MS assumes PST-1PDT-2 while GNU and other good compilers assume GMT or the configured default timezone.

Btw: the MS compiler still doesn't recognize more than 3 characters of a timezone name in tzname[], not even in its 32-bit version 6.03.
Furthermore if TZ is not set or TZ=GMT then the GNU compiler might produce an error result -1 when mktime is called with a struct tm
where the isdst flag is set to 1. If a programmer doesn't compensate for this, erroneous time calculations are done.

#include stdlib.h
#include stdio.h
#include time.h
#include sys/timeb.h

void dotime( time_t tim )
{
	struct tm* ptm;
	ptm = gmtime( &tim );
	printf( "   gmtime: %04d-%02d-%02d %02d:%02d:%02d  wday: %d  yday: %d  isdst: %d  ",
		ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday,
		ptm->tm_hour, ptm->tm_min, ptm->tm_sec,
		ptm->tm_wday, ptm->tm_yday, ptm->tm_isdst );
	printf( "mk: %ld\n", mktime( ptm ) );
	ptm = localtime( &tim );
	printf( "localtime: %04d-%02d-%02d %02d:%02d:%02d  wday: %d  yday: %d  isdst: %d  ",
		ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday,
		ptm->tm_hour, ptm->tm_min, ptm->tm_sec,
		ptm->tm_wday, ptm->tm_yday, ptm->tm_isdst );
	printf( "mk: %ld\n", mktime( ptm ) );
}


void doit()
{
	time_t tim;
	struct timeb ltimeb;

#ifdef _MSC_VER
	printf( "tzname[0]: %s  tzname[1]: %s  MS daylight: %d  timezone: %ld\n",
		tzname[0], tzname[1], daylight, (long) timezone );
#else
	printf( "tzname[0]: %s  tzname[1]: %s\n",
		tzname[0], tzname[1] );
#endif
	/* time and ftime should never differ */
	tim = time( NULL );
	printf( "     time: %ld\n", tim );
#if 0
	dotime( tim );
#endif
	ftime( <imeb );
	printf( "    ftime: %ld  dstflag: %d  timezone: %ld\n",
		ltimeb.time, ltimeb.dstflag, (long) ltimeb.timezone );
	dotime( ltimeb.time );
}


int main( int argc, char* argv[] )
{
	const char* pEnv = getenv( "TZ" );
	printf( "\nTZ=%s\n", (pEnv ? pEnv : "(null)") );
	if ( argc == 1 )
	{
		printf( "\nno tzset()\n" );
		doit();
	}
	tzset();
	printf( "\ntzset()\n" );
	doit();
	if ( argc > 1 )
	{
		struct tm atm, *ptm;
		struct timeb ltimeb;
		time_t tim;
		int year, month, day, hour, minute, second, isdst, method;

		tim = time( NULL );
		ptm = localtime( &tim );
		year = ptm->tm_year;
		month = atoi( argv[1] ) - 1;
		day = ( argc > 2 ? atoi( argv[2] ) : ptm->tm_mday );
		hour = ( argc > 3 ? atoi( argv[3] ) : ptm->tm_hour );
		minute = second = 0;

		/*	First variant to do it wrong, with tm_isdst set to zero and
			current time is not DST and the given time is DST.
		 */
		atm.tm_year	= year;
		atm.tm_mon	= month;
		atm.tm_mday	= day;
		atm.tm_hour	= hour;
		atm.tm_min	= minute;
		atm.tm_sec	= second;
		atm.tm_isdst	= 0;
		tim = mktime( &atm );
		printf( "\n 0 mktime  %04d-%02d-%02d %02d:%02d:%02d pre-isdst=0 : %ld, post-isdst=%d\n", year + 1900, month + 1, day, hour, minute, second, tim, atm.tm_isdst );
		dotime( tim );

		/*	Second variant to do it wrong, with tm_isdst set to one and
			current time is DST and the given time is not DST.
		 */
		atm.tm_year	= year;
		atm.tm_mon	= month;
		atm.tm_mday	= day;
		atm.tm_hour	= hour;
		atm.tm_min	= minute;
		atm.tm_sec	= second;
		atm.tm_isdst	= 1;
		tim = mktime( &atm );
		printf( "\n 1 mktime  %04d-%02d-%02d %02d:%02d:%02d pre-isdst=1 : %ld, post-isdst=%d\n", year + 1900, month + 1, day, hour, minute, second, tim, atm.tm_isdst );
		dotime( tim );


		/* And now do it right */
		/* Get current dstflag. Depending on how the system is setup (whether
		 * it uses local time or UTC time for hardware system time) the
		 * dstflag of a ftime call will either be set or not, regardless of
		 * the daylight saving time being in effect or not!
		 * So this might be wrong:
		ftime( <imeb );
		isdst = ( ltimeb.dstflag != 0 );
		 * Also completely wild values are possible, like ltimeb.dstflag==-216
		 * and ltimeb.timezone==2420 or ltimeb.timezone==2388, which has been
		 * seen with a gcc on SunOS. A negative value of ltimeb.dstflag could
		 * indicate that no timezone information is available.
		 * To be on the safe side you have to call localtime of the current
		 * time and use the tm_isdst value.
		 */
		tim = time( NULL );
		ptm = localtime( &tim );
		isdst = ptm->tm_isdst;
		method = 1;
		atm.tm_year	= year;
		atm.tm_mon	= month;
		atm.tm_mday	= day;
		atm.tm_hour	= hour;
		atm.tm_min	= minute;
		atm.tm_sec	= second;
		atm.tm_isdst	= isdst;
		tim = mktime( &atm );
		/* if pre-isdst and post-isdst differ we'll have to correct it */
		/* 	this still may be wrong if the time is in the hour of DST ending,
			think about it */
		if ( atm.tm_isdst != isdst )
		{	/* keep DST as what mktime says it is and convert again */
			method = 2;
			atm.tm_year	= year;
			atm.tm_mon	= month;
			atm.tm_mday	= day;
			atm.tm_hour	= hour;
			atm.tm_min	= minute;
			atm.tm_sec	= second;
			tim = mktime( &atm );
		}
		else if ( isdst && tim == -1 )
		{	/* DST set but no DST offset given in TZ, try with isdst==0 */
			/* e.g. gcc and TZ=GMT */
			method = 3;
			atm.tm_year	= year;
			atm.tm_mon	= month;
			atm.tm_mday	= day;
			atm.tm_hour	= hour;
			atm.tm_min	= minute;
			atm.tm_sec	= second;
			atm.tm_isdst	= 0;
			tim = mktime( &atm );
		}
		printf( "\n  correct  %04d-%02d-%02d %02d:%02d:%02d pre-isdst=%d : %ld, post-isdst=%d (method %d)\n", year + 1900, month + 1, day, hour, minute, second, isdst, tim, atm.tm_isdst, method );
		dotime( tim );
	}
	return 0;
}

Comments are closed.

Post Navigation