From c0eefe7fef0298b557fc235b4c47cee12de29e7d Mon Sep 17 00:00:00 2001
From: rswindell <>
Date: Sat, 23 Feb 2008 10:57:55 +0000
Subject: [PATCH] Moved time-related functions from genwrap.*  to datewrap.*.
 Moved xpDateTime and isoDateTime functions from datewrap.* to xpdatetime.*.
 Created several new xp and ISO date time functions and macros.

---
 src/xpdev/datewrap.c   | 300 ++++++-------------------------
 src/xpdev/datewrap.h   | 110 +++---------
 src/xpdev/genwrap.c    |  54 ------
 src/xpdev/genwrap.h    |  16 +-
 src/xpdev/xpdatetime.c | 395 +++++++++++++++++++++++++++++++++++++++++
 src/xpdev/xpdatetime.h | 148 +++++++++++++++
 src/xpdev/xpdev.dsp    |   4 +
 src/xpdev/xpdev_mt.dsp |   4 +
 8 files changed, 637 insertions(+), 394 deletions(-)
 create mode 100644 src/xpdev/xpdatetime.c
 create mode 100644 src/xpdev/xpdatetime.h

diff --git a/src/xpdev/datewrap.c b/src/xpdev/datewrap.c
index 37cf81e6cb..5b6a3ee970 100644
--- a/src/xpdev/datewrap.c
+++ b/src/xpdev/datewrap.c
@@ -1,6 +1,6 @@
 /* datewrap.c */
 
-/* Wrappers for Borland getdate() and gettime() functions */
+/* Wrappers for non-standard date and time functions */
 
 /* $Id$ */
 
@@ -8,7 +8,7 @@
  * @format.tab-size 4		(Plain Text/Source Code File Header)			*
  * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
  *																			*
- * Copyright 2005 Rob Swindell - http://www.synchro.net/copyright.html		*
+ * Copyright 2008 Rob Swindell - http://www.synchro.net/copyright.html		*
  *																			*
  * This library is free software; you can redistribute it and/or			*
  * modify it under the terms of the GNU Lesser General Public License		*
@@ -37,7 +37,7 @@
 
 #include "string.h"	/* memset */
 #include "genwrap.h"
-#include "datewrap.h"	/* xpDateTime_t */
+#include "datewrap.h"
 
 /* Compensates for struct tm "weirdness" */
 time_t sane_mktime(struct tm* tm)
@@ -51,256 +51,18 @@ time_t sane_mktime(struct tm* tm)
 	return mktime(tm);
 }
 
-/**************************************/
-/* Cross-platform date/time functions */
-/**************************************/
-
-xpDateTime_t xpDateTime_create(unsigned year, unsigned month, unsigned day
-							  ,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;
-}
-
-xpDateTime_t xpDateTime_now(void)
-{
-#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
-}
-
-xpTimeZone_t xpTimeZone_local(void)
-{
-#if defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DARWIN__)
-	struct tm tm;
-	time_t t;
-
-	localtime_r(&t, &tm);
-	return(tm.tm_gmtoff/60);
-#else
-#if defined(__BORLANDC__) || defined(__CYGWIN__)
-	#define timezone _timezone
-#endif
-
-	/* Converts (_)timezone from seconds west of UTC to minutes east of UTC */
-	return -timezone/60;
-#endif
-}
-
-time_t xpDateTime_to_time(xpDateTime_t xpDateTime)
-{
-	struct tm tm;
-
-	ZERO_VAR(tm);
-
-	if(xpDateTime.date.year==0)
-		return(0);
-
-	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;
-
-	return sane_mktime(&tm);
-
-}
-
-xpDateTime_t time_to_xpDateTime(time_t ti)
-{
-	xpDateTime_t	never;
-	struct tm tm;
-
-	memset(&never,0,sizeof(never));
-	if(ti==0)
-		return(never);
-
-	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_local());
-}
-
-/**********************************************/
-/* Decimal-coded ISO-8601 date/time functions */
-/**********************************************/
-
-isoDate_t xpDateTime_to_isoDateTime(xpDateTime_t xpDateTime, isoTime_t* isoTime)
-{
-	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);
-}
-
-xpDateTime_t isoDateTime_to_xpDateTime(isoDate_t date, isoTime_t ti)
-{
-	return xpDateTime_create(isoDate_year(date),isoDate_month(date),isoDate_day(date)
-		,isoTime_hour(ti),isoTime_minute(ti),(float)isoTime_second(ti),xpTimeZone_local());
-}
-
-isoDate_t time_to_isoDateTime(time_t ti, isoTime_t* isoTime)
-{
-	struct tm tm;
-
-	if(isoTime!=NULL)
-		*isoTime=0;
-
-	if(ti==0)
-		return(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);
-}
-
-isoTime_t time_to_isoTime(time_t ti)
-{
-	isoTime_t isoTime;
-	
-	time_to_isoDateTime(ti,&isoTime);
-
-	return isoTime;
-}
-
-time_t isoDateTime_to_time(isoDate_t date, isoTime_t ti)
-{
-	struct tm tm;
-
-	ZERO_VAR(tm);
-
-	if(date==0)
-		return(0);
-
-	tm.tm_year	= isoDate_year(date);
-	tm.tm_mon	= isoDate_month(date);
-	tm.tm_mday	= isoDate_day(date);
-
-	tm.tm_hour	= isoTime_hour(ti);
-	tm.tm_min	= isoTime_minute(ti);
-	tm.tm_sec	= isoTime_second(ti);
-
-	return sane_mktime(&tm);
-}
-
-BOOL isoTimeZone_parse(const char* str, xpTimeZone_t* zone)
-{
-	unsigned hour=0,minute=0;
-
-	switch(*str) {
-		case 0:		/* local time-zone */
-			*zone = xpTimeZone_local();	
-			return TRUE;	
-		case 'Z':	/* UTC */
-			*zone = 0;		
-			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;
-}
-
-xpDateTime_t isoDateTime_parse(const char* str)
-{
-	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:ss�hhmm */
-		,&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"			/* CCYYMMDDThhmmss�hhmm */
-		,&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"			/* CCYYMMDDhhmmss�hhmm */
-		,&xpDateTime.date.year
-		,&xpDateTime.date.month
-		,&xpDateTime.date.day
-		,&xpDateTime.time.hour
-		,&xpDateTime.time.minute
-		,&xpDateTime.time.second
-		,zone)>=1
-		) && isoTimeZone_parse(zone,&xpDateTime.zone))
-		return xpDateTime;
-
-	return xpDateTime;
-}
+#if !defined(__BORLANDC__)
 
 /***********************************/
 /* Borland DOS date/time functions */
 /***********************************/
 
-#if !defined(__BORLANDC__)
-
 #if defined(_WIN32)
 	#include <windows.h>	/* SYSTEMTIME and GetLocalTime() */
 #else
 	#include <sys/time.h>	/* stuct timeval, gettimeofday() */
 #endif
 
-#include "datewrap.h"	/* struct defs, verify prototypes */
-
 void xp_getdate(struct date* nyd)
 {
 	time_t tim;
@@ -339,3 +101,57 @@ void gettime(struct time* nyt)
 }
 
 #endif	/* !Borland */
+
+#if !defined(__unix__)
+
+/****************************************************************************/
+/* Win32 implementations of the recursive (thread-safe) versions of std C	*/
+/* time functions (gmtime, localtime, ctime, and asctime) used in Unix.		*/
+/* The native Win32 versions of these functions are already thread-safe.	*/
+/****************************************************************************/
+
+struct tm* DLLCALL gmtime_r(const time_t* t, struct tm* tm)
+{
+	struct tm* tmp = gmtime(t);
+
+	if(tmp==NULL)
+		return(NULL);
+
+	*tm = *tmp;
+	return(tm);
+}
+
+struct tm* DLLCALL localtime_r(const time_t* t, struct tm* tm)
+{
+	struct tm* tmp = localtime(t);
+
+	if(tmp==NULL)
+		return(NULL);
+
+	*tm = *tmp;
+	return(tm);
+}
+
+char* DLLCALL ctime_r(const time_t *t, char *buf)
+{
+	char* p = ctime(t);
+
+	if(p==NULL)
+		return(NULL);
+
+	strcpy(buf,p);
+	return(buf);
+}
+
+char* DLLCALL asctime_r(const struct tm *tm, char *buf)
+{
+	char* p = asctime(tm);
+
+	if(p==NULL)
+		return(NULL);
+
+	strcpy(buf,p);
+	return(buf);
+}
+
+#endif	/* !defined(__unix__) */
diff --git a/src/xpdev/datewrap.h b/src/xpdev/datewrap.h
index c32d1e4827..33a8a50aea 100644
--- a/src/xpdev/datewrap.h
+++ b/src/xpdev/datewrap.h
@@ -1,6 +1,6 @@
 /* datewrap.h */
 
-/* Wrappers for Borland getdate() and gettime() functions */
+/* Wrappers for non-standard date and time functions */
 
 /* $Id$ */
 
@@ -8,7 +8,7 @@
  * @format.tab-size 4		(Plain Text/Source Code File Header)			*
  * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
  *																			*
- * Copyright 2005 Rob Swindell - http://www.synchro.net/copyright.html		*
+ * Copyright 2008 Rob Swindell - http://www.synchro.net/copyright.html		*
  *																			*
  * This library is free software; you can redistribute it and/or			*
  * modify it under the terms of the GNU Lesser General Public License		*
@@ -40,79 +40,28 @@
 
 #include "genwrap.h"	/* time_t */
 
-/* Compensates for struct tm "weirdness" */
-time_t sane_mktime(struct tm*);
-
-/**************************************/
-/* Cross-platform date/time functions */
-/**************************************/
-
-typedef struct {
-	unsigned	year;
-	unsigned	month;
-	unsigned	day;
-} xpDate_t;
-
-typedef struct {
-	unsigned	hour;
-	unsigned	minute;
-	float		second;	/* supports fractional seconds */
-} xpTime_t;
-
-typedef int xpTimeZone_t;
-
-typedef struct {
-	xpDate_t		date;
-	xpTime_t		time;
-	xpTimeZone_t	zone;	/* minutes +/- UTC */
-} xpDateTime_t;
-
-xpDateTime_t	xpDateTime_create(unsigned year, unsigned month, unsigned day
-								   ,unsigned hour, unsigned minute, float second
-								   ,xpTimeZone_t);
-xpDateTime_t	xpDateTime_now(void);
-time_t			xpDateTime_to_time(xpDateTime_t);
-xpDateTime_t	time_to_xpDateTime(time_t);
-xpTimeZone_t	xpTimeZone_local(void);
-
-/**********************************************/
-/* Decimal-coded ISO-8601 date/time functions */
-/**********************************************/
-
-typedef uint32_t	isoDate_t;	/* CCYYMMDD (decimal) */
-typedef uint32_t	isoTime_t;	/* HHMMSS   (decimal) */
-
-#define			isoDate_create(year,mon,day)	(((year)*10000)+((mon)*100)+(day))
-#define			isoTime_create(hour,min,sec)	(((hour)*10000)+((min)*100)+((unsigned)sec))
-				
-#define			isoDate_year(date)				((date)/10000)
-#define			isoDate_month(date)				(((date)/100)%100)
-#define			isoDate_day(date)				((date)%100)
-												
-#define			isoTime_hour(time)				((time)/10000)
-#define			isoTime_minute(time)			(((time)/100)%100)
-#define			isoTime_second(time)			((time)%100)
-
-BOOL			isoTimeZone_parse(const char* str, xpTimeZone_t*);
-xpDateTime_t	isoDateTime_parse(const char* str);
-
-/**********************************************/
-/* Conversion between time_t and isoDate/Time */
-/**********************************************/
-isoTime_t		time_to_isoTime(time_t);
-isoDate_t		time_to_isoDateTime(time_t, isoTime_t*);
-time_t			isoDateTime_to_time(isoDate_t, isoTime_t);
-#define			time_to_isoDate(t)		time_to_isoDateTime(t,NULL)
-
-/***************************************************/
-/* Conversion between xpDate/Time and isoDate/Time */
-/***************************************************/
-
-#define			xpDate_to_isoDate(date)	isoDate_create((date).year,(date).month,(date).day)
-#define			xpTime_to_isoTime(time)	isoTime_create((time).hour,(time).minute,(unsigned)((time).second))
-
-xpDateTime_t	isoDateTime_to_xpDateTime(isoDate_t, isoTime_t);
-isoDate_t		xpDateTime_to_isoDateTime(xpDateTime_t, isoTime_t*);
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/* Implementation of mktime() that handles common tm element conversions for you */
+time_t sane_mktime(struct tm* tm);
+
+/*********************************************************************************/
+/* Win32 implementations of recursive (thread-safe) std C time functions on Unix */
+/*********************************************************************************/
+
+#if !defined(__unix__)	
+
+	#include <time.h>		/* time_t, etc. */
+
+	DLLEXPORT struct tm*    DLLCALL		gmtime_r(const time_t* t, struct tm* tm);
+	DLLEXPORT struct tm*    DLLCALL		localtime_r(const time_t* t, struct tm* tm);
+	DLLEXPORT char*	        DLLCALL		ctime_r(const time_t *t, char *buf);
+	DLLEXPORT char*	        DLLCALL		asctime_r(const struct tm *tm, char *buf);
+	DLLEXPORT char*			DLLCALL		strtok_r(char *str, const char *delim, char **last);
+
+#endif
 
 /***********************************/
 /* Borland DOS date/time functions */
@@ -137,19 +86,14 @@ struct time {
 	unsigned char ti_sec;
 };
 
-#if defined(__cplusplus)
-extern "C" {
-#endif
-
 #define getdate(x)	xp_getdate(x)
 void xp_getdate(struct date*);
 void gettime(struct time*);
 
+#endif	/* !Borland */
+
 #if defined(__cplusplus)
 }
 #endif
 
-#endif	/* !Borland */
-
-#endif	/* Don't add anything after this line */
-
+#endif	/* Don't add anything after this line */
\ No newline at end of file
diff --git a/src/xpdev/genwrap.c b/src/xpdev/genwrap.c
index 9d0df87e1f..b42f897a0d 100644
--- a/src/xpdev/genwrap.c
+++ b/src/xpdev/genwrap.c
@@ -428,60 +428,6 @@ char* DLLCALL os_cmdshell(void)
 	return(shell);
 }
 
-#if !defined(__unix__)
-
-/****************************************************************************/
-/* Win32 implementations of the recursive (thread-safe) versions of std C	*/
-/* time functions (gmtime, localtime, ctime, and asctime) used in Unix.		*/
-/* The native Win32 versions of these functions are already thread-safe.	*/
-/****************************************************************************/
-
-struct tm* DLLCALL gmtime_r(const time_t* t, struct tm* tm)
-{
-	struct tm* tmp = gmtime(t);
-
-	if(tmp==NULL)
-		return(NULL);
-
-	*tm = *tmp;
-	return(tm);
-}
-
-struct tm* DLLCALL localtime_r(const time_t* t, struct tm* tm)
-{
-	struct tm* tmp = localtime(t);
-
-	if(tmp==NULL)
-		return(NULL);
-
-	*tm = *tmp;
-	return(tm);
-}
-
-char* DLLCALL ctime_r(const time_t *t, char *buf)
-{
-	char* p = ctime(t);
-
-	if(p==NULL)
-		return(NULL);
-
-	strcpy(buf,p);
-	return(buf);
-}
-
-char* DLLCALL asctime_r(const struct tm *tm, char *buf)
-{
-	char* p = asctime(tm);
-
-	if(p==NULL)
-		return(NULL);
-
-	strcpy(buf,p);
-	return(buf);
-}
-
-#endif	/* !defined(__unix__) */
-
 /****************************************************************/
 /* Microsoft (DOS/Win32) real-time system clock implementation.	*/
 /****************************************************************/
diff --git a/src/xpdev/genwrap.h b/src/xpdev/genwrap.h
index 56d93ceea4..96915ff64a 100644
--- a/src/xpdev/genwrap.h
+++ b/src/xpdev/genwrap.h
@@ -40,6 +40,7 @@
 
 #include <stdio.h>		/* sprintf */
 #include <string.h>		/* strerror() */
+#include <time.h>		/* clock_t */
 #include "gen_defs.h"	/* ulong */
 #include "wrapdll.h"	/* DLLEXPORT and DLLCALL */
 
@@ -289,24 +290,9 @@ DLLEXPORT int DLLCALL	get_errno(void);
 #endif
 
 /* Win32 implementations of recursive (thread-safe) std C time functions on Unix */
-
 #if !defined(__unix__)	
 
-	#include <time.h>		/* time_t, etc. */
-
-	DLLEXPORT struct tm*    DLLCALL		gmtime_r(const time_t* t, struct tm* tm);
-	DLLEXPORT struct tm*    DLLCALL		localtime_r(const time_t* t, struct tm* tm);
-	DLLEXPORT char*	        DLLCALL		ctime_r(const time_t *t, char *buf);
-	DLLEXPORT char*	        DLLCALL		asctime_r(const struct tm *tm, char *buf);
 	DLLEXPORT char*			DLLCALL		strtok_r(char *str, const char *delim, char **last);
-
-#endif
-
-#if defined(__solaris__)
-	#define CTIME_R(x,y)	ctime_r(x,y)
-	/* #define CTIME_R(x,y)	ctime_r(x,y,sizeof y) */
-#else
-	#define CTIME_R(x,y)	ctime_r(x,y)
 #endif
 
 /* Mimic the Borland randomize() and random() CRTL functions */
diff --git a/src/xpdev/xpdatetime.c b/src/xpdev/xpdatetime.c
new file mode 100644
index 0000000000..694c9875c2
--- /dev/null
+++ b/src/xpdev/xpdatetime.c
@@ -0,0 +1,395 @@
+/* xpdatetime.c */
+
+/* Cross-platform (and eXtra Precision) date/time functions */
+
+/* $Id$ */
+
+/****************************************************************************
+ * @format.tab-size 4		(Plain Text/Source Code File Header)			*
+ * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
+ *																			*
+ * Copyright 2008 Rob Swindell - http://www.synchro.net/copyright.html		*
+ *																			*
+ * 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 */
+/**************************************/
+
+xpDateTime_t xpDateTime_create(unsigned year, unsigned month, unsigned day
+							  ,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;
+}
+
+xpDateTime_t xpDateTime_now(void)
+{
+#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
+}
+
+xpTimeZone_t xpTimeZone_local(void)
+{
+#if defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DARWIN__)
+	struct tm tm;
+	time_t t;
+
+	localtime_r(&t, &tm);
+	return(tm.tm_gmtoff/60);
+#else
+#if defined(__BORLANDC__) || defined(__CYGWIN__)
+	#define timezone _timezone
+#endif
+
+	/* Converts (_)timezone from seconds west of UTC to minutes east of UTC */
+	return -timezone/60;
+#endif
+}
+
+time_t xpDateTime_to_time(xpDateTime_t xpDateTime)
+{
+	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;
+
+	return sane_mktime(&tm);
+}
+
+xpDateTime_t time_to_xpDateTime(time_t time, xpTimeZone_t zone)
+{
+	xpDateTime_t	never;
+	struct tm tm;
+
+	memset(&never,0,sizeof(never));
+
+	ZERO_VAR(tm);
+	if(localtime_r(&time,&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==xpTimeZone_LOCAL ? xpTimeZone_local() : zone);
+}
+
+xpDateTime_t gmtime_to_xpDateTime(time_t ti)
+{
+	xpDateTime_t	never;
+	struct tm tm;
+
+	memset(&never,0,sizeof(never));
+
+	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 */
+/**********************************************/
+
+isoDate_t xpDateTime_to_isoDateTime(xpDateTime_t xpDateTime, isoTime_t* isoTime)
+{
+	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);
+}
+
+xpDateTime_t isoDateTime_to_xpDateTime(isoDate_t date, isoTime_t ti)
+{
+	return xpDateTime_create(isoDate_year(date),isoDate_month(date),isoDate_day(date)
+		,isoTime_hour(ti),isoTime_minute(ti),(float)isoTime_second(ti),xpTimeZone_local());
+}
+
+isoDate_t time_to_isoDateTime(time_t ti, isoTime_t* isoTime)
+{
+	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);
+}
+
+isoTime_t time_to_isoTime(time_t ti)
+{
+	isoTime_t isoTime;
+	
+	time_to_isoDateTime(ti,&isoTime);
+
+	return isoTime;
+}
+
+isoDate_t gmtime_to_isoDateTime(time_t ti, isoTime_t* isoTime)
+{
+	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);
+}
+
+isoTime_t gmtime_to_isoTime(time_t ti)
+{
+	isoTime_t isoTime;
+	
+	gmtime_to_isoDateTime(ti,&isoTime);
+
+	return isoTime;
+}
+
+time_t isoDateTime_to_time(isoDate_t date, isoTime_t time)
+{
+	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);
+
+	tm.tm_hour	= isoTime_hour(time);
+	tm.tm_min	= isoTime_minute(time);
+	tm.tm_sec	= isoTime_second(time);
+
+	return sane_mktime(&tm);
+}
+
+/****************************************************************************/
+/* Conversion from xpDate/Time/Zone to isoDate/Time/Zone Strings			*/
+/****************************************************************************/
+
+char* xpDate_to_isoDateStr(xpDate_t date, const char* sep, char* str, size_t maxlen)
+{
+	if(sep==NULL)
+		sep="-";
+
+	snprintf(str,maxlen,"%04lu%s%02lu%s%02lu"
+		,date.year	,sep
+		,date.month	,sep
+		,date.day);
+
+	return str;
+}
+
+char* xpTime_to_isoTimeStr(xpTime_t time, const char* sep, int precision
+								   ,char* str, size_t maxlen)
+{
+	if(sep==NULL)
+		sep=":";
+
+	snprintf(str, maxlen, "%02lu%s%02lu%s%0*.*f"
+		,time.hour		,sep
+		,time.minute	,sep
+		,precision ? (precision+3) : 2
+		,precision
+		,time.second
+		);
+
+	return str;
+}
+
+char* xpTimeZone_to_isoTimeZoneStr(xpTimeZone_t zone, const char* sep
+								   ,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;
+}
+
+char* xpDateTime_to_isoDateTimeStr(xpDateTime_t dt
+								   ,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								*/
+/****************************************************************************/
+
+BOOL isoTimeZoneStr_parse(const char* str, xpTimeZone_t* zone)
+{
+	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 */
+xpDateTime_t isoDateTimeStr_parse(const char* str)
+{
+	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:ss�hhmm */
+		,&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"			/* CCYYMMDDThhmmss�hhmm */
+		,&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"			/* CCYYMMDDhhmmss�hhmm */
+		,&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;
+}
diff --git a/src/xpdev/xpdatetime.h b/src/xpdev/xpdatetime.h
new file mode 100644
index 0000000000..fe65c2ba59
--- /dev/null
+++ b/src/xpdev/xpdatetime.h
@@ -0,0 +1,148 @@
+/* xpdatetime.h */
+
+/* Cross-platform (and eXtra Precision) date/time functions */
+
+/* $Id$ */
+
+/****************************************************************************
+ * @format.tab-size 4		(Plain Text/Source Code File Header)			*
+ * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
+ *																			*
+ * Copyright 2008 Rob Swindell - http://www.synchro.net/copyright.html		*
+ *																			*
+ * 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.	*
+ ****************************************************************************/
+
+#ifndef _XPDATETIME_H_
+#define _XPDATETIME_H_
+
+#include "gen_defs.h"	/* uint32_t and time_t */
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/**************************************/
+/* Cross-platform date/time functions */
+/**************************************/
+
+#define INVALID_TIME	(time_t)-1	/* time_t representation of an invalid date/time */
+
+typedef struct {
+	unsigned	year;	/* 0-9999 */
+	unsigned	month;	/* 1-12 */
+	unsigned	day;	/* 1-31 */
+} xpDate_t;
+
+typedef struct {
+	unsigned	hour;	/* 0-23 */
+	unsigned	minute;	/* 0-59 */
+	float		second;	/* 0.0-59.999, supports fractional seconds */
+} xpTime_t;
+
+typedef int xpTimeZone_t;
+#define xpTimeZone_UTC		0
+#define xpTimeZone_LOCAL	1
+
+typedef struct {
+	xpDate_t		date;
+	xpTime_t		time;
+	xpTimeZone_t	zone;	/* minutes +/- UTC */
+} xpDateTime_t;
+
+xpDateTime_t	xpDateTime_create(unsigned year, unsigned month, unsigned day
+								   ,unsigned hour, unsigned minute, float second
+								   ,xpTimeZone_t);
+xpDateTime_t	xpDateTime_now(void);
+time_t			xpDateTime_to_time(xpDateTime_t);
+xpDateTime_t	time_to_xpDateTime(time_t, xpTimeZone_t);
+xpDateTime_t	gmtime_to_xpDateTime(time_t);
+xpTimeZone_t	xpTimeZone_local(void);
+
+/**********************************************/
+/* Decimal-coded ISO-8601 date/time functions */
+/**********************************************/
+
+typedef uint32_t	isoDate_t;	/* CCYYMMDD (decimal) */
+typedef uint32_t	isoTime_t;	/* HHMMSS   (decimal) */
+
+#define			isoDate_create(year,mon,day)	(((year)*10000)+((mon)*100)+(day))
+#define			isoTime_create(hour,min,sec)	(((hour)*10000)+((min)*100)+((unsigned)sec))
+				
+#define			isoDate_year(date)				((date)/10000)
+#define			isoDate_month(date)				(((date)/100)%100)
+#define			isoDate_day(date)				((date)%100)
+												
+#define			isoTime_hour(time)				((time)/10000)
+#define			isoTime_minute(time)			(((time)/100)%100)
+#define			isoTime_second(time)			((time)%100)
+
+BOOL			isoTimeZoneStr_parse(const char* str, xpTimeZone_t*);
+xpDateTime_t	isoDateTimeStr_parse(const char* str);
+
+/**************************************************************/
+/* Conversion between time_t (local and GMT) and isoDate/Time */
+/**************************************************************/
+isoTime_t		time_to_isoTime(time_t);
+isoTime_t		gmtime_to_isoTime(time_t);
+isoDate_t		time_to_isoDateTime(time_t, isoTime_t*);
+isoDate_t		gmtime_to_isoDateTime(time_t, isoTime_t*);
+time_t			isoDateTime_to_time(isoDate_t, isoTime_t);
+#define			time_to_isoDate(t)		time_to_isoDateTime(t,NULL)
+#define			gmtime_to_isoDate(t)	gmtime_to_isoDateTime(t,NULL)
+
+/***************************************************/
+/* Conversion between xpDate/Time and isoDate/Time */
+/***************************************************/
+
+#define			xpDate_to_isoDate(date)	isoDate_create((date).year,(date).month,(date).day)
+#define			xpTime_to_isoTime(time)	isoTime_create((time).hour,(time).minute,(unsigned)((time).second))
+
+xpDateTime_t	isoDateTime_to_xpDateTime(isoDate_t, isoTime_t);
+isoDate_t		xpDateTime_to_isoDateTime(xpDateTime_t, isoTime_t*);
+
+/*****************************************************************/
+/* Conversion from xpDate/Time/Zone to isoDate/Time/Zone Strings */
+/*****************************************************************/
+char* xpDate_to_isoDateStr(xpDate_t
+						,const char* sep
+						,char* str, size_t maxlen);
+char* xpTime_to_isoTimeStr(xpTime_t
+						,const char* sep
+						,int precision
+						,char* str, size_t maxlen);
+char* xpTimeZone_to_isoTimeZoneStr(xpTimeZone_t
+						,const char* sep
+						,char *str, size_t maxlen);
+char* xpDateTime_to_isoDateTimeStr(xpDateTime_t
+						,const char* date_sep, const char* datetime_sep, const char* time_sep
+						,int precision
+						,char* str, size_t maxlen);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif	/* Don't add anything after this line */
\ No newline at end of file
diff --git a/src/xpdev/xpdev.dsp b/src/xpdev/xpdev.dsp
index f7d5796800..54bde292a9 100644
--- a/src/xpdev/xpdev.dsp
+++ b/src/xpdev/xpdev.dsp
@@ -126,6 +126,10 @@ SOURCE=.\str_list.c
 # End Source File
 # Begin Source File
 
+SOURCE=.\xpdatetime.c
+# End Source File
+# Begin Source File
+
 SOURCE=.\xpprintf.c
 # End Source File
 # End Target
diff --git a/src/xpdev/xpdev_mt.dsp b/src/xpdev/xpdev_mt.dsp
index a753098699..cb5fea3ed3 100644
--- a/src/xpdev/xpdev_mt.dsp
+++ b/src/xpdev/xpdev_mt.dsp
@@ -134,6 +134,10 @@ SOURCE=.\threadwrap.c
 # End Source File
 # Begin Source File
 
+SOURCE=.\xpdatetime.c
+# End Source File
+# Begin Source File
+
 SOURCE=.\xpprintf.c
 # End Source File
 # End Target
-- 
GitLab