/*
datetime.cpp

Функции для работы с датой и временем
*/
/*
Copyright (c) 1996 Eugene Roshal
Copyright (c) 2000 Far Group
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. The name of the authors may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "headers.hpp"

#include "datetime.hpp"
#include "lang.hpp"
#include "language.hpp"
#include "config.hpp"
#include "strmix.hpp"

#define range(low, item, hi) Max(low, Min(item, hi))

static FARString AMonth[2][12], AWeekday[2][7], Month[2][12], Weekday[2][7];

static int CurLang = -1, WeekFirst = 0;

DWORD ConvertYearToFull(DWORD ShortYear)
{
	DWORD UpperBoundary = 0;
	//	if(!GetCalendarInfo(LOCALE_USER_DEFAULT, CAL_GREGORIAN, CAL_ITWODIGITYEARMAX|CAL_RETURN_NUMBER, nullptr, 0, &UpperBoundary))
	{
		UpperBoundary = 2029;	// Magic, current default value.
	}
	return (UpperBoundary / 100 - (ShortYear < UpperBoundary % 100 ? 0 : 1)) * 100 + ShortYear;
}

int GetDateFormat()
{
	// 0 = month-day-year
	// 1 = day-month-year
	// 2 = year-month-day
	// other = 2

	//int Result = 1;
	//	GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_IDATE|LOCALE_RETURN_NUMBER,reinterpret_cast<LPWSTR>(&Result),sizeof(Result)/sizeof(WCHAR));
	//return Result;
	return (Opt.DateFormat >= 0 && Opt.DateFormat <= 2) ? Opt.DateFormat : GetDateFormatDefault();
}

wchar_t GetDateSeparator()
{
	//	wchar_t Info[100];
	//	GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_SDATE,Info,ARRAYSIZE(Info));
	//	return *Info;
	//return L'/';
	return Opt.strDateSeparator.IsEmpty() ? GetDateSeparatorDefault() : Opt.strDateSeparator.At(0);
}

wchar_t GetTimeSeparator()
{
	//	wchar_t Info[100];
	//	GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_STIME,Info,ARRAYSIZE(Info));
	//	return *Info;
	//return L':';
	return Opt.strTimeSeparator.IsEmpty() ? GetTimeSeparatorDefault() : Opt.strTimeSeparator.At(0);
}

void PrepareStrFTime()
{
	// DWORD Loc[]={LANG_ENGLISH,LANG_NEUTRAL},ID;

	//	GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_IFIRSTDAYOFWEEK|LOCALE_RETURN_NUMBER,reinterpret_cast<LPWSTR>(&WeekFirst),sizeof(WeekFirst)/sizeof(WCHAR));

	for (int i = 0; i < 2; i++) {
		const wchar_t *months[12] = {L"January", L"February", L"March", L"April", L"May", L"June", L"July",
				L"August", L"September", L"October", L"November", L"December"};
		const wchar_t *amonths[12] = {L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", L"Jul", L"Aug", L"Sep",
				L"Oct", L"Nov", L"Dec"};
		for (int j = 0; j < 12; ++j) {
			Month[i][j] = months[j];
			AMonth[i][j] = amonths[j];
		}

		const wchar_t *days[7] = {L"Monday", L"Tuesday", L"Wednesday", L"Thursday", L"Friday", L"Saturday",
				L"Sunday"};
		const wchar_t *adays[7] = {L"Mon", L"Tue", L"Wed", L"Thu", L"Fri", L"Sat", L"Sun"};
		for (int j = 0; j < 7; ++j) {
			Weekday[i][j] = days[j];
			AWeekday[i][j] = adays[j];
		}
		/*		LCID CurLCID=MAKELCID(MAKELANGID(Loc[i],SUBLANG_DEFAULT),SORT_DEFAULT);

				for (ID=LOCALE_SMONTHNAME1; ID<=LOCALE_SMONTHNAME12; ID++)
				{
					int size=GetLocaleInfo(CurLCID,ID,nullptr,0);
					LPWSTR lpwszTemp=Month[i][ID-LOCALE_SMONTHNAME1].GetBuffer(size);
					GetLocaleInfo(CurLCID,ID,lpwszTemp,size);
					*lpwszTemp=Upper(*lpwszTemp);
					Month[i][ID-LOCALE_SMONTHNAME1].ReleaseBuffer();
				}

				for (ID=LOCALE_SABBREVMONTHNAME1; ID<=LOCALE_SABBREVMONTHNAME12; ID++)
				{
					int size=GetLocaleInfo(CurLCID,ID,nullptr,0);
					LPWSTR lpwszTemp=AMonth[i][ID-LOCALE_SABBREVMONTHNAME1].GetBuffer(size);
					GetLocaleInfo(CurLCID,ID,lpwszTemp,size);
					*lpwszTemp=Upper(*lpwszTemp);
					AMonth[i][ID-LOCALE_SABBREVMONTHNAME1].ReleaseBuffer();
				}

				for (ID=LOCALE_SDAYNAME1; ID<=LOCALE_SDAYNAME7; ID++)
				{
					int size=GetLocaleInfo(CurLCID,ID,nullptr,0);
					LPWSTR lpwszTemp=Weekday[i][ID-LOCALE_SDAYNAME1].GetBuffer(size);
					GetLocaleInfo(CurLCID,ID,lpwszTemp,size);
					*lpwszTemp=Upper(*lpwszTemp);
					Weekday[i][ID-LOCALE_SDAYNAME1].ReleaseBuffer();
				}

				for (ID=LOCALE_SABBREVDAYNAME1; ID<=LOCALE_SABBREVDAYNAME7; ID++)
				{
					int size=GetLocaleInfo(CurLCID,ID,nullptr,0);
					LPWSTR lpwszTemp=AWeekday[i][ID-LOCALE_SABBREVDAYNAME1].GetBuffer(size);
					GetLocaleInfo(CurLCID,ID,lpwszTemp,size);
					*lpwszTemp=Upper(*lpwszTemp);
					AWeekday[i][ID-LOCALE_SABBREVDAYNAME1].ReleaseBuffer();
				}*/
	}

	CurLang = 0;
}

static void atime(FARString &strDest, const tm *tmPtr)
{
	// Thu Oct 07 12:37:32 1999
	strDest.Format(L"%ls %ls %02d %02d:%02d:%02d %4d",
			AWeekday[CurLang]
					[!WeekFirst ? ((tmPtr->tm_wday + 6) % 7) : (!(tmPtr->tm_wday) ? 6 : tmPtr->tm_wday - 1)]
							.CPtr(),
			AMonth[CurLang][tmPtr->tm_mon].CPtr(), tmPtr->tm_mday, tmPtr->tm_hour, tmPtr->tm_min,
			tmPtr->tm_sec, tmPtr->tm_year + 1900);
}

static void st_time(FARString &strDest, const tm *tmPtr, const wchar_t chr)
{
	int DateSeparator = GetDateSeparator();

	if (chr == L'v') {
		strDest.Format(L"%2d-%3.3ls-%4d", range(1, tmPtr->tm_mday, 31),
				AMonth[CurLang][range(0, tmPtr->tm_mon, 11)].CPtr(), tmPtr->tm_year + 1900);
		strDest.Upper(3, 3);
	} else
		switch (GetDateFormat()) {
			case 0:
				strDest.Format(L"%02d%lc%02d%lc%4d", tmPtr->tm_mon + 1, DateSeparator, tmPtr->tm_mday,
						DateSeparator, tmPtr->tm_year + 1900);
				break;
			case 1:
				strDest.Format(L"%02d%lc%02d%lc%4d", tmPtr->tm_mday, DateSeparator, tmPtr->tm_mon + 1,
						DateSeparator, tmPtr->tm_year + 1900);
				break;
			default:
				strDest.Format(L"%4d%lc%02d%lc%02d", tmPtr->tm_year + 1900, DateSeparator, tmPtr->tm_mon + 1,
						DateSeparator, tmPtr->tm_mday);
				break;
		}
}

// weeknumber --- figure how many weeks into the year
static int weeknumber(const tm *timeptr, const int firstweekday)
{
	int wday = timeptr->tm_wday;

	if (firstweekday == 1) {
		if (!wday)	// sunday
			wday = 6;
		else
			wday--;
	}

	int ret = ((timeptr->tm_yday + 7 - wday) / 7);

	if (ret < 0)
		ret = 0;

	return ret;
}

// isleap --- is a year a leap year?
static int isleap(const int year)
{
	return ((!(year % 4) && (year % 100)) || !(year % 400));
}

static int iso8601wknum(const tm *timeptr)
{
	/*
	From 1003.2:
	If the week (Monday to Sunday) containing January 1
	has four or more days in the new year, then it is week 1;
	otherwise it is the highest numbered week of the previous
	year (52 or 53), and the next week is week 1.

	ADR: This means if Jan 1 was Monday through Thursday,
	it was week 1, otherwise week 52 or 53.

	XPG4 erroneously included POSIX.2 rationale text in the
	main body of the standard. Thus it requires week 53.
	*/
	// get week number, Monday as first day of the week
	int weeknum = weeknumber(timeptr, 1);
	/*
	With thanks and tip of the hatlo to tml@tik.vtt.fi

	What day of the week does January 1 fall on?
	We know that
	(timeptr->tm_yday - jan1.tm_yday) MOD 7 ==
		(timeptr->tm_wday - jan1.tm_wday) MOD 7
	and that
	jan1.tm_yday == 0
	and that
	timeptr->tm_wday MOD 7 == timeptr->tm_wday
	from which it follows that. . .
	 */
	int jan1day = timeptr->tm_wday - (timeptr->tm_yday % 7);

	if (jan1day < 0)
		jan1day+= 7;

	/*
	 * If Jan 1 was a Monday through Thursday, it was in
	 * week 1. Otherwise it was last year's highest week, which is
	 * this year's week 0.
	 *
	 * What does that mean?
	 * If Jan 1 was Monday, the week number is exactly right, it can
	 * never be 0.
	 * If it was Tuesday through Thursday, the weeknumber is one
	 * less than it should be, so we add one.
	 * Otherwise, Friday, Saturday or Sunday, the week number is
	 * OK, but if it is 0, it needs to be 52 or 53.
	 */
	switch (jan1day) {
		case 1:		// Monday
			break;
		case 2:		// Tuesday
		case 3:		// Wednesday
		case 4:		// Thursday
			weeknum++;
			break;
		case 5:		// Friday
		case 6:		// Saturday
		case 0:		// Sunday

			if (!weeknum) {
#ifdef USE_BROKEN_XPG4
				/* XPG4 (as of March 1994) says 53 unconditionally */
				weeknum = 53;
#else
				// get week number of last week of last year
				// 12/31 last year
				tm dec31ly = *timeptr;
				dec31ly.tm_year--;
				dec31ly.tm_mon = 11;
				dec31ly.tm_mday = 31;
				dec31ly.tm_wday = !jan1day ? 6 : jan1day - 1;
				dec31ly.tm_yday = 364 + isleap(dec31ly.tm_year + 1900);
				weeknum = iso8601wknum(&dec31ly);
#endif
			}

			break;
	}

	if (timeptr->tm_mon == 11) {
		/*
		The last week of the year
		can be in week 1 of next year.
		Sigh.

		This can only happen if
		M   T  W
		29  30 31
		30  31
		31
		*/
		if ((timeptr->tm_wday == 1 && (timeptr->tm_mday >= 29 && timeptr->tm_mday <= 31))
				|| (timeptr->tm_wday == 2 && (timeptr->tm_mday == 30 || timeptr->tm_mday == 31))
				|| (timeptr->tm_wday == 3 && timeptr->tm_mday == 31))
			weeknum = 1;
	}

	return weeknum;
}

size_t WINAPI StrFTime(FARString &strDest, const wchar_t *Format, const tm *t)
{
	if (CurLang == -1 && Lang.IsLanguageLoaded())
		PrepareStrFTime();

	// меняем язык.
	CurLang = 0;
	size_t Len;

	for (Len = 1; *Format; Format++) {
		if (*Format != L'%') {
			Len++;
			const wchar_t Text[] = {*Format, 0};
			strDest+= Text;
		}
#if 1
		else {
			FARString strBuf;

			switch (*++Format) {
				case L'L':
					CurLang = !CurLang;
					continue;
					// Краткое имя дня недели (Sun,Mon,Tue,Wed,Thu,Fri,Sat)
					// abbreviated weekday name
				case L'a':
					strBuf = AWeekday[CurLang][!WeekFirst ? ((t->tm_wday + 6) % 7)
														: (!t->tm_wday ? 6 : t->tm_wday - 1)];
					break;
					// Полное имя дня недели
					// full weekday name
				case L'A':
					strBuf =
							Weekday[CurLang]
								[!WeekFirst ? ((t->tm_wday + 6) % 7) : (!t->tm_wday ? 6 : t->tm_wday - 1)];
					break;
					// Краткое имя месяца (Jan,Feb,...)
					// abbreviated month name
				case L'h':
				case L'b':
					strBuf = AMonth[CurLang][t->tm_mon];
					break;
					// Полное имя месяца
					// full month name
				case L'B':
					strBuf = Month[CurLang][t->tm_mon];
					break;
					// Дата и время в формате WDay Mnt Day HH:MM:SS yyyy
					// appropriate date and time representation
				case L'c':
					atime(strBuf, t);
					break;
					// Столетие как десятичное число (00 - 99). Например, 1992 => 19
				case L'C':
					strBuf.Format(L"%02d", (t->tm_year + 1900) / 100);
					break;
					// day of month, blank padded
				case L'e':
					// Две цифры дня месяца (01 - 31)
					// day of the month, 01 - 31
				case L'd':
					strBuf.Format(*Format == L'e' ? L"%2d" : L"%02d", t->tm_mday);
					break;
					// hour, 24-hour clock, blank pad
				case L'k':
					// Две цифры часа (00 - 23)
					// hour, 24-hour clock, 00 - 23
				case L'H':
					strBuf.Format(*Format == L'k' ? L"%2d" : L"%02d", t->tm_hour);
					break;
					// hour, 12-hour clock, 1 - 12, blank pad
				case L'l':
					// Две цифры часа (01 - 12)
					// hour, 12-hour clock, 01 - 12
				case L'I': {
					int I = t->tm_hour % 12;

					if (!I)
						I = 12;

					strBuf.Format(*Format == L'l' ? L"%2d" : L"%02d", I);
					break;
				}
				// Три цифры дня в году (001 - 366)
				// day of the year, 001 - 366
				case L'j':
					strBuf.Format(L"%03d", t->tm_yday + 1);
					break;
					// Две цифры месяца, как десятичное число (1 - 12)
					// month, 01 - 12
				case L'm': {
					// %mh - Hex month digit
					// %m0 - ведущий 0
					const wchar_t *fmt = Format[1] == L'h' ? L"%X" : Format[1] == L'0' ? L"%02d" : L"%d";

					if (fmt[1] != L'd')
						Format++;

					strBuf.Format(fmt, t->tm_mon + 1);
					break;
				}
				// Две цифры минут (00 - 59)
				// minute, 00 - 59
				case L'M':
					strBuf.Format(L"%02d", t->tm_min);
					break;
					// AM или PM
					// am or pm based on 12-hour clock
				case L'p':
					strBuf = (t->tm_hour / 12) ? L"PM" : L"AM";
					break;
					// Две цифры секунд (00 - 59)
					// second, 00 - 59
				case L'S':
					strBuf.Format(L"%02d", t->tm_sec);
					break;
					// День недели где 0 - Воскресенье (Sunday) (0 - 6)
					// weekday, Sunday == 0, 0 - 6
				case L'w':
					strBuf.Format(L"%d", t->tm_wday);
					break;
					// Две цифры номера недели, где Воскресенье (Sunday)
					// является первым днем недели (00 - 53)
					// week of year, Sunday is first day of week
				case L'U':
					// Две цифры номера недели, где Понедельник (Monday)
					// является первым днем недели (00 - 53)
					// week of year, Monday is first day of week
				case L'W': {
					int I = t->tm_wday - (t->tm_yday % 7);

					// I = (chr == 'W'?(!WeekFirst?((t->tm_wday+6)%7):(t->tm_wday? t->tm_wday-1:6)):(t->tm_wday)) - (t->tm_yday % 7);
					if (I < 0)
						I+= 7;

					strBuf.Format(L"%02d", (t->tm_yday + I - (*Format == L'W')) / 7);
					break;
				}
				// date as dd-bbb-YYYY
				case L'v':
					// Дата в формате mm.dd.yyyy
					// appropriate date representation
				case L'D':
				case L'x':
					st_time(strBuf, t, *Format);
					break;
					// Время в формате HH:MM:SS
					// appropriate time representation
				case L'T':
				case L'X': {
					int TimeSeparator = GetTimeSeparator();
					strBuf.Format(L"%02d%c%02d%c%02d", t->tm_hour, TimeSeparator, t->tm_min, TimeSeparator,
							t->tm_sec);
					break;
				}
				// Две цифры года без столетия (00 to 99)
				// year without a century, 00 - 99
				case L'y':
					strBuf.Format(L"%02d", t->tm_year % 100);
					break;
					// Год со столетием (19yy-20yy)
					// year with century
				case L'Y':
					strBuf.Format(L"%d", 1900 + t->tm_year);
					break;
					// Имя часового пояса или пусто, если часовой пояс не задан
				case L'Z':
					// todo strBuf.Format(L"%+03d%02d",-(_timezone/3600),-(_timezone/60)%60);
					// Ptr = _tzname[ t->tm_isdst ];
					break;
					// same as \n
				case L'n':
					strBuf = L"\n";
					break;
					// same as \t
				case L't':
					strBuf = L"\t";
					break;
				case L'%':
					strBuf = L"%";
					break;
					// time as %I:%M:%S %p
				case L'r':
					StrFTime(strBuf, L"%I:%M:%S %p", t);
					break;
					// time as %H:%M
				case L'R':
					StrFTime(strBuf, L"%H:%M", t);
					break;
					// week of year according ISO 8601
				case L'V':
					strBuf.Format(L"%02d", iso8601wknum(t));
					break;
			}

			strDest+= strBuf;
			Len+= strBuf.GetLength();
		}
#endif
	}

	if (*Format)
		return 0;

	return Len - 1;
}

size_t MkStrFTime(FARString &strDest, const wchar_t *Fmt)
{
	tm *time_now;
	time_t secs_now;
	// todo _tzset();
	time(&secs_now);
	time_now = localtime(&secs_now);

	if (!Fmt || !*Fmt)
		Fmt = Opt.Macro.strDateFormat;

	return StrFTime(strDest, Fmt, time_now);
}

uint64_t FileTimeToUI64(const FILETIME *ft)
{
	ULARGE_INTEGER uli;
	uli.LowPart = ft->dwLowDateTime;
	uli.HighPart = ft->dwHighDateTime;
	return uli.QuadPart;
}

int64_t FileTimeDifference(const FILETIME *a, const FILETIME *b)
{
	return (int64_t)FileTimeToUI64(a) - (int64_t)FileTimeToUI64(b);
}

void GetFileDateAndTime(const wchar_t *Src, LPWORD Dst, size_t Count, int Separator)
{
	for (size_t i = 0; i < Count; i++) {
		Dst[i] = (WORD)-1;
	}

	FARString strDigit;
	const wchar_t *Ptr = Src;

	for (size_t i = 0; i < Count; i++) {
		Ptr = GetCommaWord(Ptr, strDigit, Separator);

		if (Ptr) {
			const wchar_t *PtrDigit = strDigit;

			while (*PtrDigit && !isdigit(*PtrDigit))	// todo iswdigit
			{
				PtrDigit++;
			}

			if (*PtrDigit) {
				Dst[i] = static_cast<WORD>(_wtoi(PtrDigit));
			}
		} else {
			break;
		}
	}
}

void StrToDateTime(const wchar_t *CDate, const wchar_t *CTime, FILETIME &ft, int DateFormat,
		int DateSeparator, int TimeSeparator, bool bRelative)
{
	WORD DateN[3]{}, TimeN[4]{};
	SYSTEMTIME st{};
	// Преобразуем введённые пользователем дату и время
	GetFileDateAndTime(CDate, DateN, ARRAYSIZE(DateN), DateSeparator);
	GetFileDateAndTime(CTime, TimeN, ARRAYSIZE(TimeN), TimeSeparator);

	if (!bRelative) {
		if (DateN[0] == (WORD)-1 || DateN[1] == (WORD)-1 || DateN[2] == (WORD)-1) {
			// Пользователь оставил дату пустой, значит обнулим дату и время.
			memset(&ft, 0, sizeof(ft));
			return;
		}

		// "Оформим"
		switch (DateFormat) {
			case 0:
				st.wMonth = DateN[0];
				st.wDay = DateN[1];
				st.wYear = DateN[2];
				break;
			case 1:
				st.wDay = DateN[0];
				st.wMonth = DateN[1];
				st.wYear = DateN[2];
				break;
			default:
				st.wYear = DateN[0];
				st.wMonth = DateN[1];
				st.wDay = DateN[2];
				break;
		}

		if (st.wYear < 100) {
			st.wYear = static_cast<WORD>(ConvertYearToFull(st.wYear));
		}
	} else {
		st.wDay = DateN[0] != (WORD)-1 ? DateN[0] : 0;
	}

	st.wHour = TimeN[0] != (WORD)-1 ? (TimeN[0]) : 0;
	st.wMinute = TimeN[1] != (WORD)-1 ? (TimeN[1]) : 0;
	st.wSecond = TimeN[2] != (WORD)-1 ? (TimeN[2]) : 0;
	st.wMilliseconds = TimeN[3] != (WORD)-1 ? (TimeN[3]) : 0;

	// преобразование в "удобоваримый" формат
	if (bRelative) {
		ULARGE_INTEGER time;
		time.QuadPart = st.wMilliseconds;
		time.QuadPart+= (UINT64)st.wSecond * 1000;
		time.QuadPart+= (UINT64)st.wMinute * 1000 * 60;
		time.QuadPart+= (UINT64)st.wHour * 1000 * 60 * 60;
		time.QuadPart+= (UINT64)st.wDay * 1000 * 60 * 60 * 24;
		time.QuadPart*= 10000;
		ft.dwLowDateTime = time.LowPart;
		ft.dwHighDateTime = time.HighPart;
	} else {
		FILETIME lft{};

		if (WINPORT(SystemTimeToFileTime)(&st, &lft)) {
			WINPORT(LocalFileTimeToFileTime)(&lft, &ft);
		}
	}
}

static bool Init = false; // for ConvertDate()

void ConvertDate_ResetInit()
{
	Init = false;
}

void ConvertDate(const FILETIME &ft, FARString &strDateText, FARString &strTimeText, int TimeLength,
		int Brief, int TextMonth, int FullYear, int DynInit)
{
	static int WDateFormat;
	static wchar_t WDateSeparator, WTimeSeparator, WDecimalSeparator;
	//static bool Init = false;
	static SYSTEMTIME lt;
	int DateFormat;
	wchar_t DateSeparator, TimeSeparator, DecimalSeparator;

	if (!Init) {
		WDateFormat = GetDateFormat();
		WDateSeparator = GetDateSeparator();
		WTimeSeparator = GetTimeSeparator();
		WDecimalSeparator = GetDecimalSeparator();
		WINPORT(GetLocalTime)(&lt);
		Init = true;
	}

	DateFormat = DynInit ? GetDateFormat() : WDateFormat;
	DateSeparator = DynInit ? GetDateSeparator() : WDateSeparator;
	TimeSeparator = DynInit ? GetTimeSeparator() : WTimeSeparator;
	DecimalSeparator = DynInit ? GetDecimalSeparator() : WDecimalSeparator;
	int CurDateFormat = DateFormat;

	if (Brief && CurDateFormat == 2)
		CurDateFormat = 0;

	SYSTEMTIME st;
	FILETIME ct;

	if (!ft.dwHighDateTime) {
		strDateText.Clear();
		strTimeText.Clear();
		return;
	}

	WINPORT(FileTimeToLocalFileTime)(&ft, &ct);
	WINPORT(FileTimeToSystemTime)(&ct, &st);
	// if ( !strTimeText.IsEmpty() )
	{
		const wchar_t *Letter = L"";

		if (TimeLength == 6) {
			Letter = (st.wHour < 12) ? L"a" : L"p";

			if (st.wHour > 12)
				st.wHour-= 12;

			if (!st.wHour)
				st.wHour = 12;
		}

		if (TimeLength < 7)
			strTimeText.Format(L"%02d%c%02d%ls", st.wHour, TimeSeparator, st.wMinute, Letter);
		else {
			FARString strFullTime;
			strFullTime.Format(L"%02d%c%02d%c%02d%c%03d", st.wHour, TimeSeparator, st.wMinute, TimeSeparator,
					st.wSecond, DecimalSeparator, st.wMilliseconds);
			strTimeText.Format(L"%.*ls", TimeLength, strFullTime.CPtr());
		}
	}
	// if ( !strDateText.IsEmpty() )
	{
		int Year = st.wYear;

		if (!FullYear)
			Year%= 100;

		if (TextMonth) {
			const wchar_t *Month = (Msg::MonthJan + st.wMonth - 1);

			switch (CurDateFormat) {
				case 0:
					strDateText.Format(L"%3.3ls %2d %02d", Month, st.wDay, Year);
					break;
				case 1:
					strDateText.Format(L"%2d %3.3ls %02d", st.wDay, Month, Year);
					break;
				default:
					strDateText.Format(L"%02d %3.3ls %2d", Year, Month, st.wDay);
					break;
			}
		} else {
			int p1, p2, p3 = Year;
			int w1 = 2, w2 = 2, w3 = 2;
			wchar_t f1 = L'0', f2 = L'0', f3 = FullYear == 2 ? L' ' : L'0';
			switch (CurDateFormat) {
				case 0:
					p1 = st.wMonth;
					p2 = st.wDay;
					break;
				case 1:
					p1 = st.wDay;
					p2 = st.wMonth;
					break;
				default:
					p1 = Year;
					w1 = FullYear == 2 ? 5 : 2;
					f3 = f1;
					f1 = L' ';
					p2 = st.wMonth;
					p3 = st.wDay;
					break;
			}
			FormatString Fmt;
			Fmt << fmt::FillChar(f1) << fmt::Expand(w1) << p1 << DateSeparator << fmt::FillChar(f2)
				<< fmt::Expand(w2) << p2 << DateSeparator << fmt::FillChar(f3) << fmt::Expand(w3) << p3;
			strDateText = std::move(Fmt.strValue());
		}
	}

	if (Brief) {
		strDateText.Truncate(TextMonth ? 6 : 5);

		if (lt.wYear != st.wYear)
			strTimeText.Format(L"%5d", st.wYear);
	}
}

void ConvertRelativeDate(const FILETIME &ft, FARString &strDaysText, FARString &strTimeText)
{
	uint64_t t64 = FileTimeToUI64(&ft);
	UINT64 ms = (t64/= 10000) % 1000;
	UINT64 s = (t64/= 1000) % 60;
	UINT64 m = (t64/= 60) % 60;
	UINT64 h = (t64/= 60) % 24;
	UINT64 d = t64/= 24;

	FormatString DaysText;
	DaysText << d;
	strDaysText = std::move(DaysText.strValue());

	FormatString TimeText;
	TimeText << fmt::Expand(2) << fmt::FillChar(L'0') << h << GetTimeSeparator() << fmt::Expand(2)
				<< fmt::FillChar(L'0') << m << GetTimeSeparator() << fmt::Expand(2) << fmt::FillChar(L'0')
				<< s << GetDecimalSeparator() << fmt::Expand(3) << fmt::FillChar(L'0') << ms;
	strTimeText = std::move(TimeText.strValue());
}
