Convert Time Zone Start/End Rule to Specific Date

2022-09-07T12:11:39.58+00:00

I'm using timezoneapi.h library to get the local time zone information into my native C++ app. I am at a loss on how to convert the 'First Sunday of March' rule to a specific date for a given year. I'm looking for an algorithm that will do this. Unfortunately, I am limited to C++11/Visual Studio 2015 and no .NET.

Can someone provide or point me to a blog that shows how to do this?

GetTimeZoneInformationForYear() provides the correct TIME_ZONE_INFORMATION structure. StandardDate and DaylightDate fields are set to the rule, not a date, because wYear is 0.

This blog shows how this all works but doesn't go to the step of converting the rule to a date:
https://devblogs.microsoft.com/oldnewthing/20110311-00/?p=11243

Developer technologies | C++
{count} votes

2 answers

Sort by: Most helpful
  1. Barry Schwarz 3,746 Reputation points
    2022-09-08T00:55:56.677+00:00

    One easy way to find the date of the first Sunday in March is to use the standard functions mktime and localtime:

    1. Set a struct tm to March 1 of the year in question. (Remember that months and years are offset.)
    2. Pass the address of the struct to mktime and save the return value in a time_t variable.
    3. Pass the address of the time_t variable to localtime and save the return value in a struct tm*.
    4. Copy the data pointed to by the pointer into your struct tm.

    At this point, struct member tm_wday contains the weekday of the 1st. If not already Sunday, simply compute the number of days till the next Sunday and adjust the date accordingly. Once you have this date, you can use it for whatever you are doing with timezoneapi.


  2. David Lowndes 4,726 Reputation points
    2022-09-08T22:00:12.15+00:00

    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 );  
     }  
    }  
      
    

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.