/* Copyright (c) Robert Walker, support@tunesmithy.co.uk
 * Free source. Do what you wish with it - treat it as you would
 * example code in a book on c programming.
 */ 
#include "time64.h"
// #undef time
// #undef localtime
// #undef mktime
// #undef difftime
// #undef gmtime
// #undef time_t

#include <time.h>
#include <windows.h>
#include <assert.h>

#define SECS_TO_FT_MULT 10000000
#ifdef _DEBUG
#define DEBUG_TIME_T
#endif

/* From MSVC help:
 * The gmtime, mktime, and localtime functions use the same single, 
 * statically allocated structure to hold their results. Each call to 
 * one of these functions destroys the result of any previous call. 
 * If timer represents a date before midnight, January 1, 1970, 
 * gmtime returns NULL. There is no error return.
 *
 * So here is the struct to use for our 64 bit implementation
 *
 * However, it may be useful to be able to make this thread safe
 */

#ifdef USE_THREAD_LOCAL_VARIABLES
#define  TIME64_THREAD_LOCAL _declspec(thread)
#else//if (!TIME64_THREAD_LOCAL)
#define TIME64_THREAD_LOCAL
#endif

TIME64_THREAD_LOCAL static struct tm today_ret;

static void T64ToFileTime(t64 *pt,FILETIME *pft)
{
 LARGE_INTEGER li;    
 *pt*=SECS_TO_FT_MULT;
 li.QuadPart=*pt;
 pft->dwLowDateTime=li.LowPart;
 pft->dwHighDateTime=li.HighPart;
}

static void FileTimeToT64(FILETIME *pft,t64 *pt)
{
 LARGE_INTEGER li;    
 li.LowPart = pft->dwLowDateTime;
 li.HighPart = pft->dwHighDateTime;
 *pt=li.QuadPart;
 *pt/=SECS_TO_FT_MULT;
}

#define  FindTimeTBase() (t64) 11644473600
 // calculated using
/**
static t64 FindTimeTBase(void)
{
 // Find 1st Jan 1970 as a FILETIME 
 SYSTEMTIME st;
 FILETIME ft;
 memset(&st,0,sizeof(st));
 st.wYear=1970;
 st.wMonth=1;
 st.wDay=1;
 SystemTimeToFileTime(&st, &ft);
 FileTimeToT64(&ft,&tbase);
 return tbase;
}
**/

static void SystemTimeToT64(SYSTEMTIME *pst,t64 *pt)
{
 FILETIME ft;
 SystemTimeToFileTime(pst, &ft);
 FileTimeToT64(&ft,pt);
 *pt-=FindTimeTBase();
}

static void T64ToSystemTime(t64 *pt,SYSTEMTIME *pst)
{
 FILETIME ft;
 t64 t=*pt;
 t+=FindTimeTBase();
 T64ToFileTime(&t,&ft);
 FileTimeToSystemTime(&ft,pst);
}


t64 time_64(t64 *pt)
{
 t64 t;
 SYSTEMTIME st;
 GetSystemTime(&st);
 SystemTimeToT64(&st,&t);
#ifdef DEBUG_TIME_T
 {
  time_t t2=time(NULL);
  if(t2>=0)
   assert(abs(t2-(int)t)<=1);
   // the <=1 here is in case the seconds get incremented
   // betweeen the GetSystemTime(..) call and the time(..) call
 }
#endif
 if(pt)
  *pt=t;
 return t;
}

double difftime_64(t64 time1, t64 time0)
{
 return (double)(time1-time0);
}

t64 mktime64( struct tm *today )
{
 t64 t;
 SYSTEMTIME st;
 st.wDay=(WORD)today->tm_mday;
 st.wDayOfWeek=(WORD)today->tm_wday;
 st.wHour=(WORD)today->tm_hour;
 st.wMinute=(WORD)today->tm_min;
 st.wMonth=(WORD)(today->tm_mon+1);
 st.wSecond=(WORD)today->tm_sec;
 st.wYear=(WORD)(today->tm_year+1900);
 st.wMilliseconds=0;
 SystemTimeToT64(&st,&t);
 return t;
}

#define DAY_IN_SECS (60*60*24)
struct tm *gmtime_64(t64 t)
{
 SYSTEMTIME st;
 T64ToSystemTime(&t,&st);
 today_ret.tm_wday=st.wDayOfWeek;
 today_ret.tm_min=st.wMinute;
 today_ret.tm_sec=st.wSecond;
 today_ret.tm_mon=st.wMonth-1;
 today_ret.tm_mday=st.wDay;
 today_ret.tm_hour=st.wHour;
 today_ret.tm_year=st.wYear-1900;
 {
  SYSTEMTIME styear;
  t64 t64Year;
  memset(&styear,0,sizeof(styear));
  styear.wYear=st.wYear;
  styear.wMonth=1;
  styear.wDay=1;
  SystemTimeToT64(&styear,&t64Year);
  today_ret.tm_yday=(int)((t-t64Year)/DAY_IN_SECS);
 }
 today_ret.tm_isdst=0;
#ifdef DEBUG_TIME_T
 {
  struct tm today2;
  long t32=(int)t;
  if(t32>=0)
  {
   today2=*gmtime(&t32);
   assert(today_ret.tm_yday==today2.tm_yday);
   assert(today_ret.tm_wday==today2.tm_wday);
   assert(today_ret.tm_min==today2.tm_min);
   assert(today_ret.tm_sec==today2.tm_sec);
   assert(today_ret.tm_mon==today2.tm_mon);
   assert(today_ret.tm_mday==today2.tm_mday);
   assert(today_ret.tm_hour==today2.tm_hour);
   assert(today_ret.tm_year==today2.tm_year);
  }
 }
 {
  t64 t2=mktime64(&today_ret);
  assert(t2==t);
 }
#endif
 return &today_ret;
}

struct tm *localtime_64(t64 *pt)
{
 t64 t=*pt;
 FILETIME ft,ftlocal;
 T64ToFileTime(&t,&ft);
 FileTimeToLocalFileTime(&ft,&ftlocal);
 FileTimeToT64(&ftlocal,&t);
 today_ret=*gmtime_64(t);
 {
  TIME_ZONE_INFORMATION TimeZoneInformation;
  switch(GetTimeZoneInformation(&TimeZoneInformation))
  {
  case TIME_ZONE_ID_DAYLIGHT:today_ret.tm_isdst=1;break;
  case TIME_ZONE_ID_STANDARD:today_ret.tm_isdst=0;break;
  case TIME_ZONE_ID_UNKNOWN:today_ret.tm_isdst=-1;break;
  }
 }
 return &today_ret;
}

