Files
2026-04-29 17:10:47 +02:00

786 lines
26 KiB
C++

/*****************************************************************************/
/* DateTime.cpp Copyright (c) Ladislav Zezula 2009 */
/*---------------------------------------------------------------------------*/
/* Description: Conversion module TEXT <==> FILETIME */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 24.06.09 1.00 Lad Created */
/*****************************************************************************/
#include "FileTest.h"
#include "resource.h"
//-----------------------------------------------------------------------------
// Local variables
static PSYSTEMTIME pTempSt = NULL;
static LPCTSTR szTempDateTime = NULL;
static LPTSTR szStringBufferBegin = NULL;
static LPTSTR szStringBuffer = NULL;
static LPTSTR szStringBufferEnd = NULL;
static BOOL bDateConverted = FALSE;
static BOOL bTimeConverted = FALSE;
//-----------------------------------------------------------------------------
// Local functions
static bool IsSpecialFileTime(FILETIME & ft)
{
// File time with high bit set or file time with high part set to zero
return ((ft.dwHighDateTime & 0x80000000) || (ft.dwHighDateTime == 0x00000000));
}
static DWORD GetDateFormatToken(LPCTSTR szDateFormat, LPDWORD pdwTokenLength)
{
TCHAR chFirstChar = *szDateFormat;
DWORD dwEqualChars = 0;
DWORD dwToken = 0;
// Ignore empty strings
if(*szDateFormat != 0)
{
// Remember the first char from the date/time format
chFirstChar = *szDateFormat++;
dwToken = chFirstChar;
dwEqualChars++;
// Keep going till there is the same character
while(*szDateFormat == chFirstChar)
{
// Add the next character to the token
// If the token is longer than 4 equal character,
// the extra characters are simply cut.
if(dwEqualChars < 4)
{
dwToken = (dwToken << 0x08) | *szDateFormat;
dwEqualChars++;
}
// Nove to the next character in the token
szDateFormat++;
}
}
// Return the result
if(pdwTokenLength != NULL)
*pdwTokenLength = dwEqualChars;
return dwToken;
}
static WORD LocaleNameToNumber(LPCTSTR szStr, LCID lcMin, LCID lcMax, LCID lcExtra, int * piNameLength)
{
TCHAR szLocaleName[80];
LCID lcid;
int nLength;
// Set the length to zero as default
*piNameLength = 0;
// We have to go from December downto January. THe reason behind this
// is that we might confuse "1" with "10".
for(lcid = lcMax; lcid >= lcMin; lcid--)
{
// Get the localized month name
nLength = GetLocaleInfo(LOCALE_USER_DEFAULT, lcid, szLocaleName, _countof(szLocaleName));
if(nLength == 0)
break;
// If the month name is identical, we found it
if(!_tcsnicmp(szStr, szLocaleName, (nLength - 1)))
{
*piNameLength = (nLength - 1);
return (WORD)(lcid - lcMin + 1);
}
}
// Last chance: check extra locale (for example, 13th month name)
if(lcExtra != 0)
{
// Get the localized 13th month name
nLength = GetLocaleInfo(LOCALE_USER_DEFAULT, lcExtra, szLocaleName, _countof(szLocaleName));
if(nLength > 1)
{
// If the month name is identical, we found it
if(!_tcsnicmp(szStr, szLocaleName, (nLength - 1)))
{
*piNameLength = (nLength - 1);
return 13;
}
}
}
// Invalid or unrecognized locale name
return 0xFFFF;
}
// Converts locale-specific month name to month number
// Only converts genitives
static WORD MonthNameGenitiveToNumber(LPCTSTR szStr, int * piMonthNameLength)
{
SYSTEMTIME stTemp = {0};
TCHAR szMonthName[80];
int nLength;
// Prepare the temporary SYSTEMTIME structure
// Note: The recommended way to get month name as genitive is to use GetDateFormat
// with "ddMMMM" and compare month name starting at second position
stTemp.wDay = 1;
stTemp.wYear = 2000;
// Go through all month names.
for(stTemp.wMonth = 1; stTemp.wMonth <= 13; stTemp.wMonth++)
{
// Get genitive of month name
nLength = GetDateFormat(LOCALE_USER_DEFAULT, 0, &stTemp, _T("ddMMMM"), szMonthName, _countof(szMonthName));
if(nLength < 3)
break;
// Compare the month name
if(!_tcsnicmp(szStr, &szMonthName[2], nLength - 3))
{
*piMonthNameLength = (nLength - 3);
return stTemp.wMonth;
}
}
// Month name genitive not recognized
return 0xFFFF;
}
// Try to recognize the AM/PM string
static BOOL AmPmNameToWord(LPCTSTR szStr, WORD & wHourModifier, int & nAmPmNameLength)
{
TCHAR szAmPmStr[10];
int nLength;
// Check AM
nLength = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_S1159, szAmPmStr, _countof(szAmPmStr));
if(nLength > 0 && szStr[0] == szAmPmStr[0])
{
nAmPmNameLength = nLength;
wHourModifier = 0;
return TRUE;
}
// Check PM
nLength = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_S2359, szAmPmStr, _countof(szAmPmStr));
if(nLength > 0 && szStr[0] == szAmPmStr[0])
{
nAmPmNameLength = nLength;
wHourModifier = 12;
return TRUE;
}
// Not recognized
return FALSE;
}
static BOOL ConvertTextToDate(PSYSTEMTIME pSt, LPCTSTR szDateTimeStr, LPTSTR szDateFormat)
{
SYSTEMTIME st = {0};
LPTSTR szEndChar;
DWORD dwTokenLength;
DWORD dwToken;
BOOL bMonthConverted = FALSE;
BOOL bYearConverted = FALSE;
BOOL bDayConverted = FALSE;
WORD wDummy;
int nLength;
// Remove any leading spaces from szDateTimeStr
while(*szDateTimeStr == _T(' '))
szDateTimeStr++;
while(*szDateFormat != 0 && *szDateTimeStr != 0)
{
// Skip spaces
while(szDateTimeStr[0] == _T(' '))
szDateTimeStr++;
while(szDateFormat[0] == _T(' '))
szDateFormat++;
dwToken = GetDateFormatToken(szDateFormat, &dwTokenLength);
switch(dwToken)
{
// "d": Day of month as digits with no leading zero for single-digit days.
// "dd" : Day of month as digits with leading zero for single-digit days.
case 0x64: // 'd'
case 0x6464: // 'dd'
{
// Day must not be there yet
if(bDayConverted)
return FALSE;
// Store day number to SYSTEMTIME structure
st.wDay = (WORD)StrToInt(szDateTimeStr, &szEndChar, 10);
if(IsCharAlpha(szEndChar[0]) || st.wDay == 0 || st.wDay > 31)
return FALSE;
szDateTimeStr = szEndChar;
szDateFormat += dwTokenLength;
bDayConverted = TRUE;
break;
}
// "ddd": Day of week as a three-letter abbreviation (LOCALE_SABBREVDAYNAME)
// "dddd": Day of week as its full name (LOCALE_SDAYNAME).
case 0x646464: // 'ddd'
case 0x64646464: // 'dddd'
{
// Convert the day of week. However, ignore the result of the conversion,
// as day of week is not necessary to get the FILETIME and would require
// the user to enter correct day of week
wDummy = LocaleNameToNumber(szDateTimeStr, LOCALE_SDAYNAME1, LOCALE_SDAYNAME7, 0, &nLength);
if(wDummy == 0xFFFF)
return FALSE;
szDateFormat += dwTokenLength;
szDateTimeStr += nLength;
break;
}
// "M": Month as digits with no leading zero for single-digit months.
// "MM": Month as digits with leading zero for single-digit months.
// "MMM": Month as a three-letter abbreviation. The function
// uses the LOCALE_SABBREVMONTHNAME value associated with the specified locale.
// "MMMM": Month as its full name. The function uses the LOCALE_SMONTHNAME
// value associated with the specified locale.
case 0x4D: // 'M'
case 0x4D4D: // 'MM'
case 0x4D4D4D: // 'MMM'
case 0x4D4D4D4D: // 'MMMM'
{
// Month must not be there yet
if(bMonthConverted)
return FALSE;
// Try to convert it from genitive
st.wMonth = MonthNameGenitiveToNumber(szDateTimeStr, &nLength);
if(1 <= st.wMonth && st.wMonth <= 13)
{
szDateFormat += dwTokenLength;
szDateTimeStr += nLength;
bMonthConverted = TRUE;
break;
}
// Try to convert it from nominative of the full name
st.wMonth = LocaleNameToNumber(szDateTimeStr, LOCALE_SMONTHNAME1, LOCALE_SMONTHNAME12, LOCALE_SMONTHNAME13, &nLength);
if(1 <= st.wMonth && st.wMonth <= 13)
{
szDateFormat += dwTokenLength;
szDateTimeStr += nLength;
bMonthConverted = TRUE;
break;
}
// Try to convert it from the short name
st.wMonth = LocaleNameToNumber(szDateTimeStr, LOCALE_SABBREVMONTHNAME1, LOCALE_SABBREVMONTHNAME12, LOCALE_SABBREVMONTHNAME13, &nLength);
if(1 <= st.wMonth && st.wMonth <= 13)
{
szDateFormat += dwTokenLength;
szDateTimeStr += nLength;
bMonthConverted = TRUE;
break;
}
// Try to convert it from the number
st.wMonth = (WORD)StrToInt(szDateTimeStr, &szEndChar, 10);
if(IsCharAlpha(szEndChar[0]) || st.wMonth == 0 || st.wMonth > 12)
return FALSE;
szDateFormat += dwTokenLength;
szDateTimeStr = szEndChar;
bMonthConverted = TRUE;
break;
}
// "y": Year as last two digits, but with no leading zero for years less than 10.
// "yy": Year as last two digits, but with leading zero for years less than 10.
// "yyy": Invalid year, but we accept it
// "yyyy": Year represented by full four digits.
case 0x79: // 'y'
case 0x7979: // 'yy'
case 0x797979: // 'yyy'
case 0x79797979: // 'yyyy'
{
// Convert year as digit
st.wYear = (WORD)StrToInt(szDateTimeStr, &szEndChar, 10);
if(IsCharAlpha(szEndChar[0]))
return FALSE;
// Respect 2-digit years
if(dwTokenLength == 1 || dwTokenLength == 2)
{
if(80 <= st.wYear && st.wYear < 100)
st.wYear += 1900;
else
st.wYear += 2000;
}
// We don't accept years less than 1600
if(st.wYear < 1600)
return FALSE;
bYearConverted = TRUE;
szDateTimeStr = szEndChar;
szDateFormat += dwTokenLength;
break;
}
// All other chars are considered unexpected
default:
return FALSE;
}
// Skip equal characters
while(szDateTimeStr[0] != 0 && szDateTimeStr[0] == szDateFormat[0])
{
szDateTimeStr++;
szDateFormat++;
}
}
// If all three (day-month-year) were converted, we're done
if(bDayConverted && bMonthConverted && bYearConverted)
{
szTempDateTime = szDateTimeStr;
pSt->wDay = st.wDay;
pSt->wMonth = st.wMonth;
pSt->wYear = st.wYear;
return TRUE;
}
return FALSE;
}
static BOOL ConvertTextToTime(PSYSTEMTIME pSt, LPCTSTR szDateTimeStr, LPTSTR szTimeFormat)
{
SYSTEMTIME st = {0};
LPTSTR szEndChar;
DWORD dwTokenLength;
DWORD dwToken;
WORD wHourModifier = 0; // 0 or 12, depends on AM/PM string
BOOL bHourConverted = FALSE;
BOOL bMinuteConverted = FALSE;
BOOL bSecondConverted = FALSE;
BOOL b12HourFormat = FALSE;
BOOL bAmPmFound = FALSE;
int nLength;
// Remove any leading spaces from szDateTimeStr
while(*szDateTimeStr == _T(' '))
szDateTimeStr++;
while(*szTimeFormat != 0 && *szDateTimeStr != 0)
{
// Skip spaces
while(szDateTimeStr[0] == _T(' '))
szDateTimeStr++;
while(szTimeFormat[0] == _T(' '))
szTimeFormat++;
dwToken = GetDateFormatToken(szTimeFormat, &dwTokenLength);
switch(dwToken)
{
// "h": Hours with no leading zero for single-digit hours; 12-hour clock.
// "hh" : Hours with leading zero for single-digit hours; 12-hour clock.
case 0x68: // 'h'
case 0x6868: // 'hh'
{
// Store day number to SYSTEMTIME structure
st.wHour = (WORD)StrToInt(szDateTimeStr, &szEndChar, 10);
if(IsCharAlpha(szEndChar[0]) || st.wHour > 12)
return FALSE;
b12HourFormat = TRUE;
szDateTimeStr = szEndChar;
szTimeFormat += dwTokenLength;
break;
}
// "H": Hours with no leading zero for single-digit hours; 24-hour clock.
// "HH": Hours with leading zero for single-digit hours; 24-hour clock.
case 0x48: // 'H'
case 0x4848: // 'HH'
{
// Store day number to SYSTEMTIME structure
st.wHour = (WORD)StrToInt(szDateTimeStr, &szEndChar, 10);
if(IsCharAlpha(szEndChar[0]) || st.wHour > 23)
return FALSE;
bHourConverted = TRUE;
szDateTimeStr = szEndChar;
szTimeFormat += dwTokenLength;
break;
}
// "m": Minutes with no leading zero for single-digit minutes.
// "mm": Minutes with leading zero for single-digit minutes.
case 0x6D: // 'm'
case 0x6D6D: // 'mm'
{
// Store day number to SYSTEMTIME structure
st.wMinute = (WORD)StrToInt(szDateTimeStr, &szEndChar, 10);
if(IsCharAlpha(szEndChar[0]) || st.wMinute > 59)
return FALSE;
bMinuteConverted = TRUE;
szDateTimeStr = szEndChar;
szTimeFormat += dwTokenLength;
break;
}
// "s": Seconds with no leading zero for single-digit seconds.
// "ss": Seconds with leading zero for single-digit seconds.
case 0x73: // 's'
case 0x7373: // 'ss'
{
// Store day number to SYSTEMTIME structure
st.wSecond = (WORD)StrToInt(szDateTimeStr, &szEndChar, 10);
if(IsCharAlpha(szEndChar[0]) || st.wSecond > 59)
return FALSE;
bSecondConverted = TRUE;
szDateTimeStr = szEndChar;
szTimeFormat += dwTokenLength;
break;
}
// "t": One character time-marker string, such as A or P.
// "tt": Multicharacter time-marker string, such as AM or PM.
case 0x74: // 't'
case 0x7474: // 'tt'
{
// Try to recognize the string
if(AmPmNameToWord(szDateTimeStr, wHourModifier, nLength) == FALSE)
return FALSE;
bAmPmFound = TRUE;
szDateTimeStr += nLength;
szTimeFormat += dwTokenLength;
break;
}
// All other chars are considered unexpected
default:
return FALSE;
}
// Skip equal characters
while(szDateTimeStr[0] != 0 && szDateTimeStr[0] == szTimeFormat[0])
{
szDateTimeStr++;
szTimeFormat++;
}
// If both 12-hour and AM/PM string have been found,
// consider hour as converted
if(bHourConverted == FALSE && b12HourFormat && bAmPmFound)
{
st.wHour = st.wHour + wHourModifier;
bHourConverted = TRUE;
}
}
// If all three (hour-minute-second) were converted, we're done
if(bHourConverted && bMinuteConverted && bSecondConverted)
{
pSt->wHour = st.wHour;
pSt->wMinute = st.wMinute;
pSt->wSecond = st.wSecond;
return TRUE;
}
// If only hour and minute have been converted, it's OK
if(bHourConverted && bMinuteConverted && bSecondConverted == FALSE)
{
pSt->wHour = st.wHour;
pSt->wMinute = st.wMinute;
pSt->wSecond = 0;
return TRUE;
}
// If time is not present at all, we consider this as OK
if(bHourConverted == FALSE && bMinuteConverted == FALSE && bSecondConverted == FALSE )
{
pSt->wHour = 0;
pSt->wMinute = 0;
pSt->wSecond = 0;
return TRUE;
}
return FALSE;
}
static BOOL CALLBACK CollectFormatsProc(LPTSTR szFormat)
{
if(wcsstr(szStringBufferBegin, szFormat) == NULL)
{
while(szStringBuffer < szStringBufferEnd && *szFormat != 0)
*szStringBuffer++ = *szFormat++;
if(szStringBuffer < szStringBufferEnd)
*szStringBuffer++ = _T('\n');
}
return TRUE;
}
static BOOL CALLBACK EnumDateFormatsProc(LPTSTR szDateFormat)
{
if(ConvertTextToDate(pTempSt, szTempDateTime, szDateFormat))
{
bDateConverted = TRUE;
return FALSE;
}
return TRUE;
}
static BOOL CALLBACK EnumTimeFormatsProc(LPTSTR szTimeFormat)
{
if(ConvertTextToTime(pTempSt, szTempDateTime, szTimeFormat))
{
bTimeConverted = TRUE;
return FALSE;
}
return TRUE;
}
static LPTSTR FileTimeToLargeInteger(
LPTSTR szBuffer,
LPTSTR szEndChar,
PFILETIME pFt)
{
TCHAR szTemp[0x20];
// Format the filetime
StringCchPrintf(szTemp, _countof(szTemp), _T("%08lX-%08lX"), pFt->dwHighDateTime, pFt->dwLowDateTime);
// Copy to target buffer
for(int i = 0; szTemp[i] != 0; i++)
{
if(szEndChar > szBuffer)
*szBuffer++ = szTemp[i];
}
// Return the new buffer pointer
return szBuffer;
}
static int FileTimeToHumanReadableText(
LPTSTR szBuffer,
LPTSTR szEndChar,
PFILETIME pFt,
BOOL bEncloseInParentheses)
{
SYSTEMTIME st;
FILETIME lft;
LPTSTR szSaveBuffer = szBuffer;
int nLength;
// Verify special FILETIME values
if(IsSpecialFileTime(*pFt))
return 0;
// Convert the filetime to local file time
if(!FileTimeToLocalFileTime(pFt, &lft))
return 0;
if(!FileTimeToSystemTime(&lft, &st))
return 0;
// Add the opening parenthesis, if needed
if(bEncloseInParentheses && szEndChar > szBuffer)
*szBuffer++ = _T('(');
// Format the date
nLength = GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, szBuffer, (int)(szEndChar - szBuffer));
if(nLength > 0)
szBuffer += (nLength - 1);
// Add space as separator
if(szEndChar > szBuffer)
*szBuffer++ = _T(' ');
// Format the time
nLength = GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, szBuffer, (int)(szEndChar - szBuffer));
if(nLength > 0)
szBuffer += (nLength - 1);
// Add closing bracket
if(bEncloseInParentheses && szEndChar > szBuffer)
*szBuffer++ = _T(')');
return (int)(szBuffer - szSaveBuffer);
}
//-----------------------------------------------------------------------------
// Public functions
LPTSTR FileTimeToText(
LPTSTR szBuffer,
LPTSTR szBufferEnd,
PFILETIME pFt,
BOOL bTextForEdit)
{
int nLength;
if(bTextForEdit == FALSE)
{
// First part: date as LARGE_INTEGER
szBuffer = FileTimeToLargeInteger(szBuffer, szBufferEnd, pFt);
// Add one space
if(szBuffer < szBufferEnd)
*szBuffer++ = _T(' ');
// Append the filetime in human-readable form
nLength = FileTimeToHumanReadableText(szBuffer,
szBufferEnd,
pFt,
TRUE);
szBuffer += nLength;
}
else
{
// Attempt to convert the filetime to human-readable form
nLength = FileTimeToHumanReadableText(szBuffer,
szBufferEnd,
pFt,
FALSE);
// If failed, just convert it to LARGE_INTEGER
if(nLength == 0)
{
szBuffer = FileTimeToLargeInteger(szBuffer, szBufferEnd, pFt);
}
else
{
szBuffer += nLength;
}
}
// Terminate the buffer with zero
if(szBuffer < szBufferEnd)
szBuffer[0] = 0;
return szBuffer;
}
NTSTATUS TextToFileTime(LPCTSTR szText, PFILETIME pFt)
{
SYSTEMTIME st = {0};
FILETIME ft;
LPTSTR szEndChar = NULL;
// Prepare the local variables to be filled by callbacks
bDateConverted = FALSE;
bTimeConverted = FALSE;
szTempDateTime = szText;
pTempSt = &st;
// Check for "current"
if(!_tcsicmp(szText, _T("current")))
{
GetSystemTimeAsFileTime(pFt);
return STATUS_SUCCESS;
}
// Check for "-1" and "-2"
if(szText[0] == _T('-'))
{
LARGE_INTEGER IntValue64;
IntValue64.QuadPart = _tcstol(szText, &szEndChar, 10);
if(szEndChar[0] == 0)
{
pFt->dwHighDateTime = IntValue64.HighPart;
pFt->dwLowDateTime = IntValue64.LowPart;
return STATUS_SUCCESS;
}
}
// Try to convert the filetime from human-readable form
if(bDateConverted == FALSE)
EnumDateFormats(EnumDateFormatsProc, LOCALE_USER_DEFAULT, DATE_SHORTDATE);
if(bDateConverted == FALSE)
EnumDateFormats(EnumDateFormatsProc, LOCALE_USER_DEFAULT, DATE_LONGDATE);
if(bTimeConverted == FALSE)
EnumTimeFormats(EnumTimeFormatsProc, LOCALE_USER_DEFAULT, 0);
// If both date and time converted, we have succeeded
if(bDateConverted && bTimeConverted)
{
if(!SystemTimeToFileTime(&st, &ft))
return STATUS_INVALID_DATA_FORMAT;
if(!LocalFileTimeToFileTime(&ft, pFt))
return STATUS_INVALID_DATA_FORMAT;
return STATUS_SUCCESS;
}
// Try to convert 64-bit value in the form of ################ or 0x################
if(Text2Hex64(szText, (PLONGLONG)&ft) != ERROR_SUCCESS)
{
// Try to convert the 64-bit value in the form of ########-########
ft.dwHighDateTime = StrToInt(szText, &szEndChar, 16);
if(szEndChar == NULL || szEndChar[0] != _T('-'))
return STATUS_INVALID_DATA_FORMAT;
ft.dwLowDateTime = StrToInt(szEndChar+1, &szEndChar, 16);
if(szEndChar[0] != 0 && szEndChar[0] != _T(' '))
return STATUS_INVALID_DATA_FORMAT;
}
// Some values are accepted as-is ("00000000-00000000", "FFFFFFFF-FFFFFFFE", "FFFFFFFF-FFFFFFFF")
if(IsSpecialFileTime(ft))
{
pFt->dwHighDateTime = ft.dwHighDateTime;
pFt->dwLowDateTime = ft.dwLowDateTime;
return STATUS_SUCCESS;
}
// Convert from local time to file time
if(LocalFileTimeToFileTime(&ft, pFt))
return STATUS_SUCCESS;
// Conversion failed
return STATUS_INVALID_DATA_FORMAT;
}
BOOL GetSupportedDateTimeFormats(
LPCTSTR szDateFormatPrefix,
LPCTSTR szTimeFormatPrefix,
LPTSTR szBuffer,
int nMaxChars)
{
// Initialize the global strings
szStringBufferBegin = szBuffer;
szStringBuffer = szBuffer;
szStringBufferEnd = szBuffer + nMaxChars - 1;
// Put the date format prefix, if any
if(szStringBuffer != NULL)
{
while(szStringBuffer < szStringBufferEnd && *szDateFormatPrefix != 0)
*szStringBuffer++ = *szDateFormatPrefix++;
}
// Put the date formats
EnumDateFormats(CollectFormatsProc, LOCALE_USER_DEFAULT, DATE_SHORTDATE);
EnumDateFormats(CollectFormatsProc, LOCALE_USER_DEFAULT, DATE_LONGDATE);
// Put the time format prefix, if any
if(szStringBuffer != NULL)
{
while(szStringBuffer < szStringBufferEnd && *szTimeFormatPrefix != 0)
*szStringBuffer++ = *szTimeFormatPrefix++;
}
// Put the time formats
EnumTimeFormats(CollectFormatsProc, LOCALE_USER_DEFAULT, 0);
// Terminate the string and return
*szStringBuffer = 0;
return TRUE;
}