This isn't polished, but I appear to get the right results. Let me know if you use it and find anything amiss with it.
// Convert the SYSTEMTIME to a FILETIME and back in order to get all members consistent - specifically the day of week value
static BOOL MakeSystemTimeConsistent( SYSTEMTIME & st )
{
BOOL bRV = FALSE;
FILETIME ft;
if ( SystemTimeToFileTime( &st, &ft ) )
{
if ( FileTimeToSystemTime( &ft, &st ) )
{
bRV = TRUE;
}
}
return bRV;
}
static void OffsetFileTime( FILETIME& ft, UINT64 offsetIn100nsUnits )
{
ULARGE_INTEGER ulft{ .LowPart = ft.dwLowDateTime, .HighPart = ft.dwHighDateTime };
ulft.QuadPart += offsetIn100nsUnits;
ft.dwLowDateTime = ulft.LowPart;
ft.dwHighDateTime = ulft.HighPart;
}
constexpr INT64 OneDay = 24ull * 60 * 60 * 1000 * 1000 * 10;
constexpr UINT64 ThirtyOneDays = 31ull * OneDay;
static SYSTEMTIME ConvertOccurenceSystemTimeForYear( const SYSTEMTIME stPartial, const WORD Year )
{
// Take a copy and set the year & day
SYSTEMTIME stFirstOfMonth{ stPartial };
// Preserve these
const auto reqdWeekDay = stFirstOfMonth.wDayOfWeek;
auto WeekNum = stFirstOfMonth.wDay;
stFirstOfMonth.wYear = Year;
stFirstOfMonth.wDay = 1;
// Values < 5 are true week numbers, 5 means the last week in a month
if ( WeekNum < 5 )
{
// Convert 1'st of month back and forth to get the weekday of the start of the month
MakeSystemTimeConsistent( stFirstOfMonth );
// Now have the start of the month weekday
auto somwd = stFirstOfMonth.wDayOfWeek;
int offsetDays;
if ( reqdWeekDay < somwd )
{
offsetDays = reqdWeekDay - somwd + 7;
}
else
{
offsetDays = reqdWeekDay - somwd;
}
// offset by the weeks
offsetDays += 7 * (WeekNum - 1);
// The first day of the month for the desired weekday
SYSTEMTIME st{ stFirstOfMonth };
st.wDay += offsetDays;
// Ensure the week day is correct for the return value
MakeSystemTimeConsistent( st );
return st;
}
else
{
// Get the last day of the month by going back 1 day from the 1'st day of the next month
// First get the FILETIME for the 1'st of this month
FILETIME ft;
if ( SystemTimeToFileTime( &stFirstOfMonth, &ft ) )
{
// Increment the time by a month
OffsetFileTime( ft, ThirtyOneDays );
// Convert back to SYSTEMTIME
SYSTEMTIME st;
if ( FileTimeToSystemTime( &ft, &st ) )
{
// We now want the start of this next month
st.wDay = 1;
MakeSystemTimeConsistent( st );
// Previous day is the last day of the month we want to go back 1 day
if ( SystemTimeToFileTime( &st, &ft ) )
{
OffsetFileTime( ft, -OneDay );
// Back to SYSTEMTIME
if ( FileTimeToSystemTime( &ft, &st ) )
{
// Now have the day of the week for the end of the month
auto dowEndOfMonth = st.wDayOfWeek;
int offsetDays;
if ( reqdWeekDay <= dowEndOfMonth )
{
offsetDays = dowEndOfMonth - reqdWeekDay;
}
else
{
offsetDays = dowEndOfMonth - reqdWeekDay + 7;
}
st.wDay -= offsetDays;
// Ensure the week day is correct for the return value
MakeSystemTimeConsistent( st );
return st;
}
}
}
}
}
// Shouldn't get here
return {};
}
Usage:
int main()
{
TIME_ZONE_INFORMATION tzi;
WORD Year = 2022;
if ( GetTimeZoneInformationForYear( Year, NULL, &tzi ) )
{
auto stDaylight = ConvertOccurenceSystemTimeForYear( tzi.DaylightDate, Year );
auto stStd = ConvertOccurenceSystemTimeForYear( tzi.StandardDate, Year );
}
}