Synchronet now requires the libarchive development package (e.g. libarchive-dev on Debian-based Linux distros, libarchive.org for more info) to build successfully.

xpdatetime.c 11.4 KB
Newer Older
1 2 3 4
/* xpdatetime.c */

/* Cross-platform (and eXtra Precision) date/time functions */

5
/* $Id: xpdatetime.c,v 1.13 2015/11/25 07:27:07 sbbs Exp $ */
6 7 8 9 10

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
11
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
 *																			*
 * This library is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU Lesser General Public License		*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU Lesser General Public License for more details: lgpl.txt or	*
 * http://www.fsf.org/copyleft/lesser.html									*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include <string.h>		/* memset */
#include "datewrap.h"	/* sane_mktime */
#include "xpdatetime.h"	/* xpDateTime_t */

/**************************************/
/* Cross-platform date/time functions */
/**************************************/

Rob Swindell's avatar
Rob Swindell committed
46
xpDateTime_t xpDateTime_create(unsigned year, unsigned month, unsigned day
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
							  ,unsigned hour, unsigned minute, float second
							  ,xpTimeZone_t zone)
{
	xpDateTime_t	xpDateTime;

	xpDateTime.date.year	= year;
	xpDateTime.date.month	= month;
	xpDateTime.date.day		= day;
	xpDateTime.time.hour	= hour;
	xpDateTime.time.minute	= minute;
	xpDateTime.time.second	= second;
	xpDateTime.zone			= zone;

	return xpDateTime;
}

Rob Swindell's avatar
Rob Swindell committed
63
xpDateTime_t xpDateTime_now(void)
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
{
#if defined(_WIN32)
	SYSTEMTIME systime;

	GetLocalTime(&systime);
	return(xpDateTime_create(systime.wYear,systime.wMonth,systime.wDay
		,systime.wHour,systime.wMinute,(float)systime.wSecond+(systime.wMilliseconds*0.001F)
		,xpTimeZone_local()));
#else	/* !Win32 (e.g. Unix) */
	struct tm tm;
	struct timeval tv;
	time_t	t;

	gettimeofday(&tv, NULL);
	t=tv.tv_sec;
	localtime_r(&t,&tm);

	return xpDateTime_create(1900+tm.tm_year,1+tm.tm_mon,tm.tm_mday
		,tm.tm_hour,tm.tm_min,(float)tm.tm_sec+(tv.tv_usec*0.00001)
		,xpTimeZone_local());
#endif
}

rswindell's avatar
rswindell committed
87
/* Return local timezone offset (in minutes) */
Rob Swindell's avatar
Rob Swindell committed
88
xpTimeZone_t xpTimeZone_local(void)
89
{
90
#if defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DARWIN__) || defined(__linux__)
91
	struct tm tm;
92
	time_t t=time(NULL);
93 94 95

	localtime_r(&t, &tm);
	return(tm.tm_gmtoff/60);
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
#elif defined(_WIN32)
	TIME_ZONE_INFORMATION	tz;
	DWORD					tzRet;

	/*****************************/
	/* Get Time-zone information */
	/*****************************/
    memset(&tz,0,sizeof(tz));
	tzRet=GetTimeZoneInformation(&tz);
	switch(tzRet) {
		case TIME_ZONE_ID_DAYLIGHT:
			tz.Bias += tz.DaylightBias;
			break;
		case TIME_ZONE_ID_STANDARD:
			tz.Bias += tz.StandardBias;
			break;
	}

	return -tz.Bias;
115
#else
116

117 118 119 120 121
#if defined(__BORLANDC__) || defined(__CYGWIN__)
	#define timezone _timezone
#endif

	/* Converts (_)timezone from seconds west of UTC to minutes east of UTC */
rswindell's avatar
rswindell committed
122
	/* Adjust for DST, assuming adjustment is always 60 minutes <sigh> */
123
	return -((timezone/60) - (daylight*60));
124 125 126
#endif
}

127
/* TODO: Supports local timezone and UTC only, currently */
Rob Swindell's avatar
Rob Swindell committed
128
time_t xpDateTime_to_time(xpDateTime_t xpDateTime)
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
{
	struct tm tm;

	ZERO_VAR(tm);

	if(xpDateTime.date.year==0)
		return(INVALID_TIME);

	tm.tm_year	= xpDateTime.date.year;
	tm.tm_mon	= xpDateTime.date.month;
	tm.tm_mday	= xpDateTime.date.day;

	tm.tm_hour	= xpDateTime.time.hour;
	tm.tm_min	= xpDateTime.time.minute;
	tm.tm_sec	= (int)xpDateTime.time.second;

145 146 147 148 149
	if(xpDateTime.zone == xpTimeZone_UTC)
		return sane_timegm(&tm);
	if(xpDateTime.zone == xpTimeZone_LOCAL || xpDateTime.zone == xpTimeZone_local())
		return sane_mktime(&tm);
	return INVALID_TIME;
150 151
}

152
/* This version ignores the timezone in xpDateTime and always uses mktime() */
Rob Swindell's avatar
Rob Swindell committed
153
time_t xpDateTime_to_localtime(xpDateTime_t xpDateTime)
154 155 156 157 158
{
	xpDateTime.zone = xpTimeZone_LOCAL;
	return xpDateTime_to_time(xpDateTime);
}

Rob Swindell's avatar
Rob Swindell committed
159
xpDateTime_t time_to_xpDateTime(time_t ti, xpTimeZone_t zone)
160 161 162 163
{
	xpDateTime_t	never;
	struct tm tm;

164
	ZERO_VAR(never);
165
	ZERO_VAR(tm);
166
	if(localtime_r(&ti,&tm)==NULL)
167 168 169 170 171 172 173
		return(never);

	return xpDateTime_create(1900+tm.tm_year,1+tm.tm_mon,tm.tm_mday
		,tm.tm_hour,tm.tm_min,(float)tm.tm_sec
		,zone==xpTimeZone_LOCAL ? xpTimeZone_local() : zone);
}

Rob Swindell's avatar
Rob Swindell committed
174
xpDate_t time_to_xpDate(time_t ti)
175 176 177 178 179 180 181 182 183 184 185 186 187
{
	xpDate_t never;
	struct tm tm;

	ZERO_VAR(never);
	ZERO_VAR(tm);
	if(localtime_r(&ti,&tm)==NULL)
		return never;

	return xpDateTime_create(1900+tm.tm_year,1+tm.tm_mon,tm.tm_mday
		,tm.tm_hour,tm.tm_min,(float)tm.tm_sec, /* zone: */0).date;
}

Rob Swindell's avatar
Rob Swindell committed
188
xpDateTime_t gmtime_to_xpDateTime(time_t ti)
189 190 191 192
{
	xpDateTime_t	never;
	struct tm tm;

193
	ZERO_VAR(never);
194 195 196 197 198 199 200 201 202 203 204 205 206
	ZERO_VAR(tm);
	if(gmtime_r(&ti,&tm)==NULL)
		return(never);

	return xpDateTime_create(1900+tm.tm_year,1+tm.tm_mon,tm.tm_mday
		,tm.tm_hour,tm.tm_min,(float)tm.tm_sec
		,xpTimeZone_UTC);
}

/**********************************************/
/* Decimal-coded ISO-8601 date/time functions */
/**********************************************/

Rob Swindell's avatar
Rob Swindell committed
207
isoDate_t xpDateTime_to_isoDateTime(xpDateTime_t xpDateTime, isoTime_t* isoTime)
208 209 210 211 212 213 214 215 216 217 218 219 220
{
	if(isoTime!=NULL)
		*isoTime=0;

	if(xpDateTime.date.year==0)
		return(0);

	if(isoTime!=NULL)
		*isoTime=isoTime_create(xpDateTime.time.hour,xpDateTime.time.minute,xpDateTime.time.second);

	return isoDate_create(xpDateTime.date.year,xpDateTime.date.month,xpDateTime.date.day);
}

Rob Swindell's avatar
Rob Swindell committed
221
xpDateTime_t isoDateTime_to_xpDateTime(isoDate_t date, isoTime_t ti)
222 223 224 225 226
{
	return xpDateTime_create(isoDate_year(date),isoDate_month(date),isoDate_day(date)
		,isoTime_hour(ti),isoTime_minute(ti),(float)isoTime_second(ti),xpTimeZone_local());
}

Rob Swindell's avatar
Rob Swindell committed
227
isoDate_t time_to_isoDateTime(time_t ti, isoTime_t* isoTime)
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
{
	struct tm tm;

	if(isoTime!=NULL)
		*isoTime=0;

	ZERO_VAR(tm);
	if(localtime_r(&ti,&tm)==NULL)
		return(0);

	if(isoTime!=NULL)
		*isoTime=isoTime_create(tm.tm_hour,tm.tm_min,tm.tm_sec);

	return isoDate_create(1900+tm.tm_year,1+tm.tm_mon,tm.tm_mday);
}

Rob Swindell's avatar
Rob Swindell committed
244
isoTime_t time_to_isoTime(time_t ti)
245 246 247 248 249 250 251 252
{
	isoTime_t isoTime;
	
	time_to_isoDateTime(ti,&isoTime);

	return isoTime;
}

Rob Swindell's avatar
Rob Swindell committed
253
isoDate_t gmtime_to_isoDateTime(time_t ti, isoTime_t* isoTime)
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
{
	struct tm tm;

	if(isoTime!=NULL)
		*isoTime=0;

	ZERO_VAR(tm);
	if(gmtime_r(&ti,&tm)==NULL)
		return(0);

	if(isoTime!=NULL)
		*isoTime=isoTime_create(tm.tm_hour,tm.tm_min,tm.tm_sec);

	return isoDate_create(1900+tm.tm_year,1+tm.tm_mon,tm.tm_mday);
}

Rob Swindell's avatar
Rob Swindell committed
270
isoTime_t gmtime_to_isoTime(time_t ti)
271 272 273 274 275 276 277 278
{
	isoTime_t isoTime;
	
	gmtime_to_isoDateTime(ti,&isoTime);

	return isoTime;
}

Rob Swindell's avatar
Rob Swindell committed
279
time_t isoDateTime_to_time(isoDate_t date, isoTime_t ti)
280 281 282 283 284 285 286 287 288 289 290 291
{
	struct tm tm;

	ZERO_VAR(tm);

	if(date==0)
		return(INVALID_TIME);

	tm.tm_year	= isoDate_year(date);
	tm.tm_mon	= isoDate_month(date);
	tm.tm_mday	= isoDate_day(date);

292 293 294
	tm.tm_hour	= isoTime_hour(ti);
	tm.tm_min	= isoTime_minute(ti);
	tm.tm_sec	= isoTime_second(ti);
295 296 297 298 299 300 301 302

	return sane_mktime(&tm);
}

/****************************************************************************/
/* Conversion from xpDate/Time/Zone to isoDate/Time/Zone Strings			*/
/****************************************************************************/

Rob Swindell's avatar
Rob Swindell committed
303
char* xpDate_to_isoDateStr(xpDate_t date, const char* sep, char* str, size_t maxlen)
304 305 306 307
{
	if(sep==NULL)
		sep="-";

308
	snprintf(str,maxlen,"%04u%s%02u%s%02u"
309 310 311 312 313 314 315
		,date.year	,sep
		,date.month	,sep
		,date.day);

	return str;
}

316 317 318 319
/* precision	example output
 * -2			"14"
 * -1			"14:02"
 * 0            "14:02:39"
rswindell's avatar
rswindell committed
320 321 322
 * 1            "14:02:39.8"
 * 2            "14:02:39.82"
 * 3            "14:02:39.829"
323
 */
Rob Swindell's avatar
Rob Swindell committed
324
char* xpTime_to_isoTimeStr(xpTime_t ti, const char* sep, int precision
325 326 327 328 329
								   ,char* str, size_t maxlen)
{
	if(sep==NULL)
		sep=":";

330
	if(precision < -1)			/* HH */
331
		snprintf(str, maxlen, "%02u", ti.hour);
332
	else if(precision < 0)		/* HH:MM */
333
		snprintf(str, maxlen, "%02u%s%02u"
334 335
			,ti.hour		,sep
			,ti.minute
336 337
			);
	else						/* HH:MM:SS[.fract] */
338
		snprintf(str, maxlen, "%02u%s%02u%s%0*.*f"
339 340
			,ti.hour		,sep
			,ti.minute		,sep
341 342
			,precision ? (precision+3) : 2
			,precision
343
			,ti.second
344
			);
345 346 347 348

	return str;
}

Rob Swindell's avatar
Rob Swindell committed
349
char* xpTimeZone_to_isoTimeZoneStr(xpTimeZone_t zone, const char* sep
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
								   ,char *str, size_t maxlen)
{
	xpTimeZone_t	tz=zone;

	if(tz==xpTimeZone_UTC)
		return "Z";

	if(sep==NULL)
		sep=":";

	if(tz<0)
		tz=-tz;

	snprintf(str,maxlen,"%c%02u%s%02u"
		,zone < 0 ? '-':'+'
		,tz/60
		,sep
		,tz%60);

	return str;
}

Rob Swindell's avatar
Rob Swindell committed
372
char* xpDateTime_to_isoDateTimeStr(xpDateTime_t dt
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
								   ,const char* date_sep, const char* datetime_sep, const char* time_sep
								   ,int precision
								   ,char* str, size_t maxlen)
{
	char			tz_str[16];
	char			date_str[16];
	char			time_str[16];
	
	if(datetime_sep==NULL)	datetime_sep="T";

	snprintf(str,maxlen,"%s%s%s%s"
		,xpDate_to_isoDateStr(dt.date, date_sep, date_str, sizeof(date_str))
		,datetime_sep
		,xpTime_to_isoTimeStr(dt.time, time_sep, precision, time_str, sizeof(time_str))
		,xpTimeZone_to_isoTimeZoneStr(dt.zone,time_sep,tz_str,sizeof(tz_str)));

	return str;
}

/****************************************************************************/
/* isoDate/Time/Zone String parsing functions								*/
/****************************************************************************/

Rob Swindell's avatar
Rob Swindell committed
396
BOOL isoTimeZoneStr_parse(const char* str, xpTimeZone_t* zone)
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
{
	unsigned hour=0,minute=0;

	switch(*str) {
		case 0:		/* local time-zone */
			*zone = xpTimeZone_local();	
			return TRUE;	
		case 'Z':	/* UTC */
			*zone = xpTimeZone_UTC;		
			return TRUE;
		case '+':
		case '-':	/* "+/- HH[:]MM" */
			if(sscanf(str+1,"%2u%*s%2u",&hour,&minute)>=1) {
				*zone = (hour*60) + minute;
				if(*str=='-')
					*zone = -(*zone);
				return TRUE;
			}
			break;
	}
	return FALSE;
}

/* TODO: adjust times in 24:xx:xx format */
Rob Swindell's avatar
Rob Swindell committed
421
xpDateTime_t isoDateTimeStr_parse(const char* str)
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
{
	char zone[16];
	xpDateTime_t	xpDateTime;

	zone[0]=0;
	ZERO_VAR(xpDateTime);

	if((sscanf(str,"%4u-%2u-%2uT%2u:%2u:%f%6s"		/* CCYY-MM-DDThh:MM:sshhmm */
		,&xpDateTime.date.year
		,&xpDateTime.date.month
		,&xpDateTime.date.day
		,&xpDateTime.time.hour
		,&xpDateTime.time.minute
		,&xpDateTime.time.second
		,zone)>=2
	||	sscanf(str,"%4u%2u%2uT%2u%2u%f%6s"			/* CCYYMMDDThhmmsshhmm */
		,&xpDateTime.date.year
		,&xpDateTime.date.month
		,&xpDateTime.date.day
		,&xpDateTime.time.hour
		,&xpDateTime.time.minute
		,&xpDateTime.time.second
		,zone)>=4
	||	sscanf(str,"%4u%2u%2u%2u%2u%f%6s"			/* CCYYMMDDhhmmsshhmm */
		,&xpDateTime.date.year
		,&xpDateTime.date.month
		,&xpDateTime.date.day
		,&xpDateTime.time.hour
		,&xpDateTime.time.minute
		,&xpDateTime.time.second
		,zone)>=1
		) && isoTimeZoneStr_parse(zone,&xpDateTime.zone))
		return xpDateTime;

	return xpDateTime;
}