Introduction
Recently we had a requirement which demands knowledge of all the time zones and doing conversions from universal time to local time and vice versa. I searched for the code which would do this, but most of them happened to be in C#, article by Mike Dimmick is quite good but this was also in C# and there was very limited information available for doing this in C++. So I thought of putting together C++ code which does this on CodeProject. The different steps involved in writing this code are explained in the following sections. (I am not an English expert, but I tried my best to make the intention as clear as possible).
Enumerating TimeZones
There are no direct API's in Windows to enumerate time zones. We need to do get all the time zone information from the registry. The following key in the registry contains a list of time zones in alphabetical order in the form of sub keys:
"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
Each subkey under the above subkey will give information about individual timezone. This individual subkey will have the following value names.
For eg: subkey ALASKAN STANDARD TIME will have information like this:
Display : (GMT - 9:00) Alaska
Dlt : Alaskan DayLight Time
Index : 0x00000003 (3)
MapID : 30,31
Std : Alaskan Standard Time
TZI : TimeZone Information in Binary Form
Of all these values, TZI happens to be critical information, which needs to be retrieved.
This value has the following data:
long Bias
long StandardBias
long DaylightBias
SYSTEMTIME StandardDate
SYSTEMTIME DaylightDate
We need to retrieve this information, first by enumerating all sub keys under "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones", then by querying values under each of the sub keys.
CTimeZoneInfoManager::EnumerateTimeZones() will have related code which would read all sub keys information of "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones".
int CTimeZoneInfoManager::GetFullTimeZoneInfoFromRegistry() will have code which will fill the necessary time zone information structures, which are required for conversion.
Finally all the time zone information is collected in the template based class CArray<CRegTimeZoneInfo*, CRegTimeZoneInfo*> in alphabetical order.
Sorting TimeZones
Now we have time zone information in alphabetical form, but we need to have this information sorted on the offset from GMT time (bias). There are several ways to do this, but I have selected qsort() function. We need to supply the starting address of the array and a callback comparison function which will get called when sorting the CArray.
We need to pass the address of the first element of CArray and a comparison function to qsort(). The comparison function will decide the criteria on which we will sort the code. We need to sort time zones based on the offset from GMT or standard bias.
int CTimeZoneInfoManager::SortTimeZoneList()
TimeZoneComparer(const void *i_TZ1, const void *i_TZ2)
will have the related code.
FINALLY...THE TIME CONVERSION
The following APIs do the necessary conversion from local time in given TimeZone to universal time.
TzSpecificLocalTimeToSystemTime()
SystemTimeToTzSpecificLocalTime()
The SystemTimeToTzSpecificLocalTime() API takes TIME_ZONE_INFORMATION, Universal time as input and returns localtime which corresponds to supplied TIME_ZONE_INFORMATION. We will fill this TIME_ZONE_INFORMATION structure from the information we retrieved from the registry. One particular thing we need to be careful here is that the information we collected from registry for "StandardName" and "DayLightName" is in ANSI form, but TIME_ZONE_INFORMATION requires these values to be in widechar or Unicode format, so we need to use MultiByteToWideChar() and WideCharToMultiByte() functions to do the necessary conversions wherever necessary.
The TzSpecificLocalTimeToSystemTime() API does exactly the reverse of the above API, that is, it takes TIME_ZONE_INFORMATION and the corresponding local time as input and converts to universal time. But unfortunately, this is available only on XP and later OS versions!(to compile the code under XP, please uncomment "" in TimeZoneInfoManager.cpp)
For the OS versions below XP, we can convert local time of currenttimezone to universal time using code like this:
int CTimeZoneInfoManager::SpecificLocalTimeToSystemTime
(SYSTEMTIME* i_stLocal, SYSTEMTIME* o_stUniversal)
{
FILETIME ft, ft_utc;
if (!(SystemTimeToFileTime(i_stLocal, &ft) &&
LocalFileTimeToFileTime(&ft, &ft_utc) &&
FileTimeToSystemTime(&ft_utc,o_stUniversal)))
{
return 0;
}
return 1;
}
But SpecificLocalTimeToSystemTime() has other limitations too. It always considers daylight bias to be -60 and there may be a difference in behaviour of TzSpecificLocalTimeToSystemTime() and SpecificLocalTimeToSystemTime() in case of daylight savings.
The following functions have related code:
int CTimeZoneInfoManager::ConvertFromLocalToUTC()
int CTimeZoneInfoManager::ConvertFromUTClToLocal()
Sample Application
The application has very basic GUI. I have used simple datetimepicker control for I/O purposes of date and time. We can choose the option of converting UTCtoLocal or LocalToUTC from the provided radio buttons. Based on the selection, datepicker used for I/P gets enabled and datepicker used for O/P gets disabled. Whenever there is a change in the I/P, output changes automatically. We can select the required timezone from the combobox.
Conclusion
I hope this would help atleast some programmers who are looking for information related to timezones and timezone conversions.
History
- 2nd January, 2006: Initial post
| You must Sign In to use this message board. |
|
|
 |
|
 |
Hi, In your component for Local Time to UTC conversion, the implementation is such that Local Time in the current time zone(set in the system) is converted to UTC.
Conversion from our required time zone is not supported i.e TzSpecificLocalTimeToSystemTime is not functioning.
Can you please tell the solution.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
You can popuplate the structure TIME_ZONE_INFORMATION with data about your time zone (including DST settings) and then use it in subsequential conversions. You need not fill it only by GetTimeZoneInformation or manually from system supported time zones in the registry.
--- Ferda
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi Guys,
I saw the post and code that is being used. I think this code does not work properly under windows XP version. I have a new algorithm defined for fixing this problem.
This algorithm is defined for the results upto precision of nano-seconds.
Let me know if this works.. ENJOY
Please email me for details on trysunil@yahoo.com
Thanks and Regards, Sunil
BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION lpTimeZoneInformation, LPSYSTEMTIME lpLocalTime, LPSYSTEMTIME lpUniversalTime) { //variable defination section TIME_ZONE_INFORMATION tzinfo; _int64 Bias = 0; SYSTEMTIME lpTempUTime, lpTempLTime ; FILETIME ft, ft_utc; ULARGE_INTEGER t;
// get the present timezone information if(::GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_UNKNOWN) { //if timezone returns 0 then return false. return false; } //conver the local time to the UTC time using current timezone. if (!(SystemTimeToFileTime(lpLocalTime, &ft) && LocalFileTimeToFileTime(&ft, &ft_utc) && FileTimeToSystemTime(&ft_utc,lpUniversalTime))) { return false; }
//copy the utc time to the temp utc time variable. lpTempUTime.wDay = lpUniversalTime->wDay; lpTempUTime.wDayOfWeek = lpUniversalTime->wDayOfWeek; lpTempUTime.wHour = lpUniversalTime->wHour; lpTempUTime.wMilliseconds = lpUniversalTime->wMilliseconds; lpTempUTime.wMinute = lpUniversalTime->wMinute; lpTempUTime.wMonth = lpUniversalTime->wMonth; lpTempUTime.wSecond = lpUniversalTime->wSecond; lpTempUTime.wYear = lpUniversalTime->wYear;
// convert the UTC Time to the local time using timezone properties. // store the local time in the temp local time variable. if(::SystemTimeToTzSpecificLocalTime(&tzinfo,&lpTempUTime,&lpTempLTime) == 0) return false;
//find the offset between the temp local time and the local time which is //the input to this function. the output to this function in is the difference // in nano seconds. Bias = CompareTime(&lpTempLTime,lpLocalTime); // Actual conversion starts here. // convert the systemtime to file time then add the offset to it // and convert the file time to the utc time. if (!::SystemTimeToFileTime(lpLocalTime, &ft)) return FALSE; //convert the file time tot he Large integer type t.HighPart = ft.dwHighDateTime; t.LowPart = ft.dwLowDateTime; //add the bias in nono seconds t.QuadPart += (LONGLONG(tzinfo.Bias) * LONGLONG(600000000)); //add daylight bias in nano seconds t.QuadPart += Bias; //convert the large integer back to filetime ft.dwLowDateTime = t.LowPart; ft.dwHighDateTime = t.HighPart; //convert it back to the system time and return the result of function. return ::FileTimeToSystemTime(&ft, lpUniversalTime); }
_int64 CompareTime(LPSYSTEMTIME lpTempLTime,LPSYSTEMTIME lpLocalTime) { // variable declaration area FILETIME ftTempLTime,ftLocalTime; ULARGE_INTEGER LiTempLTime, LiLocalTime ; // convert the systemtime to file time SystemTimeToFileTime(lpTempLTime, &ftTempLTime); SystemTimeToFileTime(lpLocalTime, &ftLocalTime); // convert the file time to the large integer type LiTempLTime.HighPart = ftTempLTime.dwHighDateTime; LiTempLTime.LowPart = ftTempLTime.dwLowDateTime; LiLocalTime.HighPart = ftLocalTime.dwHighDateTime; LiLocalTime.LowPart = ftLocalTime.dwLowDateTime; // return the difference in nano-seconds return (LiLocalTime.QuadPart - LiTempLTime.QuadPart); }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
A small change in function this will cater for all kind of timezones. <code> BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION lpTimeZoneInformation, LPSYSTEMTIME lpLocalTime, LPSYSTEMTIME lpUniversalTime) { //variable defination section TIME_ZONE_INFORMATION tzinfo; _int64 Bias = 0; SYSTEMTIME lpTempUTime, lpTempLTime ; FILETIME ft, ft_utc; ULARGE_INTEGER t; int ret; ret = ::GetTimeZoneInformation(&tzinfo); // get the present timezone information if(::GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_UNKNOWN) { //if timezone returns 0 then return false. return false; }
//conver the local time to the UTC time using current timezone and bias. if (!::SystemTimeToFileTime(lpLocalTime, &ft)) return FALSE; //convert the file time tot he Large integer type t.HighPart = ft.dwHighDateTime; t.LowPart = ft.dwLowDateTime; //add the bias in nono seconds t.QuadPart += (LONGLONG(tzinfo.Bias) * LONGLONG(600000000)); //add daylight bias in nano seconds //convert the large integer back to filetime ft.dwLowDateTime = t.LowPart; ft.dwHighDateTime = t.HighPart; //convert it back to the system time and return the result of function. if(! ::FileTimeToSystemTime(&ft, lpUniversalTime)) return false;
//copy the utc time to the temp utc time variable. lpTempUTime.wDay = lpUniversalTime->wDay; lpTempUTime.wDayOfWeek = lpUniversalTime->wDayOfWeek; lpTempUTime.wHour = lpUniversalTime->wHour; lpTempUTime.wMilliseconds = lpUniversalTime->wMilliseconds; lpTempUTime.wMinute = lpUniversalTime->wMinute; lpTempUTime.wMonth = lpUniversalTime->wMonth; lpTempUTime.wSecond = lpUniversalTime->wSecond; lpTempUTime.wYear = lpUniversalTime->wYear;
// convert the UTC Time to the local time using timezone properties. // store the local time in the temp local time variable. if(::SystemTimeToTzSpecificLocalTime(&tzinfo,&lpTempUTime,&lpTempLTime) == 0) return false;
//find the offset between the temp local time and the local time which is //the input to this function. the output to this function in is the difference // in nano seconds. Bias = CompareTime(&lpTempLTime,lpLocalTime); // Actual conversion starts here. // convert the systemtime to file time then add the offset to it // and convert the file time to the utc time. if (!::SystemTimeToFileTime(lpLocalTime, &ft)) return FALSE; //convert the file time tot he Large integer type t.HighPart = ft.dwHighDateTime; t.LowPart = ft.dwLowDateTime; //add the bias in nono seconds t.QuadPart += (LONGLONG(tzinfo.Bias) * LONGLONG(600000000)); //add daylight bias in nano seconds t.QuadPart += Bias; //convert the large integer back to filetime ft.dwLowDateTime = t.LowPart; ft.dwHighDateTime = t.HighPart; //convert it back to the system time and return the result of function. return ::FileTimeToSystemTime(&ft, lpUniversalTime); }
_int64 CompareTime(LPSYSTEMTIME lpTempLTime,LPSYSTEMTIME lpLocalTime) { // variable declaration area FILETIME ftTempLTime,ftLocalTime; ULARGE_INTEGER LiTempLTime, LiLocalTime ; // convert the systemtime to file time SystemTimeToFileTime(lpTempLTime, &ftTempLTime); SystemTimeToFileTime(lpLocalTime, &ftLocalTime); // convert the file time to the large integer type LiTempLTime.HighPart = ftTempLTime.dwHighDateTime; LiTempLTime.LowPart = ftTempLTime.dwLowDateTime; LiLocalTime.HighPart = ftLocalTime.dwHighDateTime; LiLocalTime.LowPart = ftLocalTime.dwLowDateTime; // return the difference in nano-seconds return (LiLocalTime.QuadPart - LiTempLTime.QuadPart); }
}
</code>
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
FWIW, this still won't work below Win2000 because the function SystemTimeToTzSpecificLocalTime() is not available.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Anatoly Ivasyuk wrote: FWIW, this still won't work below Win2000 because the function SystemTimeToTzSpecificLocalTime() is not available.
According to the MSDN entry for SystemTimeToTzSpecificLocalTime:Requires Windows Vista, Windows XP, Windows 2000 Professional, or Windows NT Workstation 3.5 and later.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
if(::GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_UNKNOWN) { return false; }
Thanks for the code but there's a slight problem since the passed in LPTIME_ZONE_INFORMATION is never used. To fix, all usages of tzinfo need to be replaced by the appropriate form of lpTimeZoneInformation (and the GetTimeZoneInformation() and tzinfo stuff removed). I made this change and everything works great.
Regards, Eric
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
Hello Trysunil, it's pretty good to figure out a way to convert the specific local time to UTC. can you post the cleanedup version of the code?
I also would like to know if this implementation functions identical like TzSpecificLocalTimeToSystemTime() provide by Microsoft.
any information will be helpful.
Zhi
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hello Zhi,
The implemetation is exactly similar to the microsoft implementation of TzSpecificLocalTimeToSystemTime(). I have verified the code by comparing the outputs with Microsoft functions. The results are accurate.
I guess you can use the code as it is.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi ehsiung, can you paste the cleaned-up version of the source code for this routine? The way it is not working.
thanks Zhi
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
This is my modified code that uses the passed in timezone info.
BOOL TzSpecificLocalTimeToSystemTime2(LPTIME_ZONE_INFORMATION lpTZI, LPSYSTEMTIME lpLocalTime, LPSYSTEMTIME lpUniversalTime) { _int64 Bias = 0; SYSTEMTIME lpTempUTime, lpTempLTime ; FILETIME ft; ULARGE_INTEGER t;
if (!::SystemTimeToFileTime(lpLocalTime, &ft)) return FALSE; t.HighPart = ft.dwHighDateTime; t.LowPart = ft.dwLowDateTime; t.QuadPart += (LONGLONG(lpTZI->Bias) * LONGLONG(600000000)); ft.dwLowDateTime = t.LowPart; ft.dwHighDateTime = t.HighPart; if(! ::FileTimeToSystemTime(&ft, lpUniversalTime)) return false;
lpTempUTime.wDay = lpUniversalTime->wDay; lpTempUTime.wDayOfWeek = lpUniversalTime->wDayOfWeek; lpTempUTime.wHour = lpUniversalTime->wHour; lpTempUTime.wMilliseconds = lpUniversalTime->wMilliseconds; lpTempUTime.wMinute = lpUniversalTime->wMinute; lpTempUTime.wMonth = lpUniversalTime->wMonth; lpTempUTime.wSecond = lpUniversalTime->wSecond; lpTempUTime.wYear = lpUniversalTime->wYear;
if(::SystemTimeToTzSpecificLocalTime(lpTZI, &lpTempUTime, &lpTempLTime) == 0) return false;
Bias = CompareTime(&lpTempLTime,lpLocalTime); if (!::SystemTimeToFileTime(lpLocalTime, &ft)) return FALSE; t.HighPart = ft.dwHighDateTime; t.LowPart = ft.dwLowDateTime; t.QuadPart += (LONGLONG(lpTZI->Bias) * LONGLONG(600000000)); t.QuadPart += Bias; ft.dwLowDateTime = t.LowPart; ft.dwHighDateTime = t.HighPart; return ::FileTimeToSystemTime(&ft, lpUniversalTime); }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Seems like a nice utility, but there is a little bug floating about in the code. Upon execution of the code and before the main dialog box comes up an error dialog box comes up with a message of "Invalid Property Value". After clicking ok on this dialog box, the main dialog box comes up and it does not seem to work correctly. Hmmmmmmmmmmm
-- modified at 13:40 Wednesday 4th January, 2006
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
The problem lies in the function CTimeZoneInfoManager::ConvertFromLocalToUTC(). The if/else statement that decides which conversion function to use depending on the current operating system is at fault. The "else" clause only gets compiled if you define the _XP_OR_LATER macro, so if you run this on XP after compiling without this flag no conversion actually takes place!
In any case, if you compile this for XP it won't run on an earlier version of windows (the exe won't be able to load) so the whole "if" statement is redundant and should be replaced with the code below.
#if _WIN32_WINNT >= 0x0501 //available only on XP and Later TzSpecificLocalTimeToSystemTime(&tzi,&localTime,&universalTime); #else // always available SpecificLocalTimeToSystemTime(&localTime, &universalTime); #endif
This will allow the programmer to use the newer function when compiling for XP and above, or the more generic function if he want's to support earlier versions of windows.
Having said all that, the function SpecificLocalTimeToSystemTime() doesn't seem to work anyway! I think it only converts the time between the current locale for the system and UTC, instead of using the selected locale! Maybe it should change the current locale to the selected one temporarily, do the conversion and then change back.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I Think defining _WIN32_WINNT for conditional compilation is good and better than _XP_OR_LATER macro. SpecificLocalTimeToSystemTime() works only for currenttimezone may be specific means currenttimezone. But the idea of changing currenttimezone temporarily may disrupt other applications if they depend on timezone info.I am not sure how we can get functionality of TZSpecificLocalTimeToSystemTime() on win2000. any ideas?
Best things happen when u least expect them.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
shadow79,
I use the TZSpecificLocalTimeToSystemTime on many computer with no problem.
However, some crash???
Do you find another way to fix that?
Did you say that TZSpecificLocalTimeToSystemTime change currenttimezone temporarily ???
Regards
Martin
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
SpecificLocalTimeToSystemTime does not take into account the specific time zone. I've made a variant which does and it works fine on Windows 2000: -
// //SpecificLocalTimeToSystemTime: // This fills in for TzSpecificLocalTimeToSystemTime which is // not present in versions of Windows prior to XP. It uses // the counterpart conversion function SystemTimeToTzSpecificLocalTime // to calculate the time with the reverse offset to that required. // the SYSTEMTIME result and the original local time are then // converted to 64 bit integers so that the offset in ticks can // be calculated. This is then used to apply the correction in the // required direction before converting back to SYSTEMTIME format. // int TimeZoneConverter::SpecificLocalTimeToSystemTime(const TIME_ZONE_INFORMATION& tzi, SYSTEMTIME* i_stLocal, SYSTEMTIME* o_stUniversal) { // Make the conversion in the 'wrong' direction so we can work out the hours difference SYSTEMTIME localMinusOffset; SystemTimeToTzSpecificLocalTime(const_cast<TIME_ZONE_INFORMATION *>(&tzi), i_stLocal, &localMinusOffset);
// Convert the result, and the original 'local' time input to FILETIME format FILETIME ftLocalMinusOffset, ftLocal; SystemTimeToFileTime(&localMinusOffset, &ftLocalMinusOffset); SystemTimeToFileTime(i_stLocal, &ftLocal);
// Now convert the result, and the original 'local' time input to __int64 __int64 i64LocalMinusOffset, i64Local;
i64LocalMinusOffset = ftLocalMinusOffset.dwLowDateTime + (__int64(ftLocalMinusOffset.dwHighDateTime) << 32); i64Local = ftLocal.dwLowDateTime + (__int64(ftLocal.dwHighDateTime) << 32); // Calculate the time offset value __int64 offset = i64Local - i64LocalMinusOffset;
// Now apply the offset in the correct direction i64Local += offset; FILETIME ftUniversal; ftUniversal.dwLowDateTime = long(i64Local & 0xffffffff); ftUniversal.dwHighDateTime = long((i64Local >> 32) & 0xffffffff); FileTimeToSystemTime(&ftUniversal, o_stUniversal);
return 1; }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|