Introduction
Microsoft has not updated Iran's daylight saving time information since 2009. Probably because between 2005 and 2008, Iran did not observe daylight saving time. But it was reintroduced from 21 March 2008, enforced by the Iranian Parliament.
Let's find out how it works and then fix it for the next 100 years!
Daylight Saving Time Structure
Daylight saving time information in Windows is stored in two different registry entries:



[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation]
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\Iran Standard Time]
Dynamic daylight saving time is introduced since the availability of Windows Vista, which allows the operating system to store historically accurate time zone information (this includes both past and future time zone data). Windows XP and Windows Server 2003 operating systems do not use the Dynamic DST data by default.
Here, you can see an entry regarding Iran's standard time:
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\Iran Standard Time]
"Display"="(GMT+03:30) Tehran"
"Dlt"="Iran Daylight Time"
"Std"="Iran Standard Time"
"MapID"="-1,72"
"Index"=dword:000000a0
"TZI"=hex:2e,ff,ff,ff,00,00,00,00,c4,ff,ff,ff,00,00,09,00,04,00,03,00,17,00,3b,\
00,3b,00,00,00,00,00,03,00,02,00,03,00,17,00,3b,00,3b,00,00,00
Its TZI is important for us and has this structure:
using System.Runtime.InteropServices;
namespace TimeZoneInfoEdit.Core.Contracts
{
[StructLayout(LayoutKind.Sequential)]
public struct Tzi
{
public int Bias;
public int StandardBias;
public int DaylightBias;
public TziSystemTime StandardDate;
public TziSystemTime DaylightDate;
}
}
And SystemTime
is defined as below:
using System.Runtime.InteropServices;
namespace TimeZoneInfoEdit.Core.Contracts
{
[StructLayoutAttribute(LayoutKind.Sequential)]
public struct TziSystemTime
{
public short Year;
public short Month;
public short DayOfWeek;
public short Day;
public short Hour;
public short Minute;
public short Second;
public short Milliseconds;
}
}
Now based on these structures, if our TZI data looks like the following information:
2C 01 00 00 00 00 00 00 C4 FF FF FF 00 00 0A 00 00 00 05 00 02 00 00
00 00 00 00 00 00 00 04 00 00 00 01 00 02 00 00 00 00 00 00 00
Its decoded version would be:
(little-endian) => (big-endian)
2C 01 00 00 => 00 00 01 2C = 300 Bias
00 00 00 00 => 00 00 00 00 = 0 Std Bias
C4 FF FF FF => FF FF FF C4 = 4294967236 Dlt Bias
( SYSTEM TIME ) StandardDate
00 00 => 00 00 = Year
0A 00 => 00 0A = Month
00 00 => 00 00 = Day of Week
05 00 => 00 05 = Day
02 00 => 00 02 = Hour
00 00 => 00 00 = Minutes
00 00 => 00 00 = Seconds
00 00 => 00 00 = Milliseconds
( SYSTEM TIME ) DaylightDate
00 00 => 00 00 = Year
04 00 => 00 04 = Month
00 00 => 00 00 = Day of Week
01 00 => 00 01 = Day
02 00 => 00 02 = Hour
00 00 => 00 00 = Minutes
00 00 => 00 00 = Seconds
00 00 => 00 00 = Milliseconds
There are some important tips about the SystemTime
data in TZI:
- Its '
Day
' field is not a day of month. It's the number of the week in a month. - '
Day
' field will start from number one. - '
DayOfWeek
' field will start from number zero. - If the '
Year
' is equal to zero, it means using the relative time and its data can be used for the next year as well. It's normally set to zero.
So the important part of the TZI formula is converting a normal DateTime
to the special format of the SystemTime
:
public static SystemTime ToSystemTime(DateTime time)
{
var result = new SystemTime
{
Year = 0,
Month = (short)time.Month,
DayOfWeek = (short)time.DayOfWeek,
Hour = (short)time.Hour,
Minute = (short)time.Minute,
Second = (short)time.Second,
Milliseconds = (short)time.Millisecond
};
int weekdayOfMonth = 1;
for (int dd = time.Day; dd > 7; dd -= 7)
weekdayOfMonth++;
result.Day = (short)weekdayOfMonth;
return result;
}
Converting TZI Structure to an Array of Bytes
To write our new TZI structure data in the Windows registry, we need to convert it to an array of bytes using Marshal.StructureToPtr
method:
using System;
using System.Runtime.InteropServices;
namespace TimeZoneInfoEdit.Core.Utils
{
public static class ByteUtils
{
public static T DeserializeByteArray<T>(this Byte[] data) where T : struct
{
var objSize = Marshal.SizeOf(typeof(T));
var buff = Marshal.AllocHGlobal(objSize);
Marshal.Copy(data, 0, buff, objSize);
var retStruct = (T)Marshal.PtrToStructure(buff, typeof(T));
Marshal.FreeHGlobal(buff);
return retStruct;
}
public static Byte[] SerializeByteArray<T>(this T msg) where T : struct
{
var objSize = Marshal.SizeOf(typeof(T));
var ret = new Byte[objSize];
var buff = Marshal.AllocHGlobal(objSize);
Marshal.StructureToPtr(msg, buff, true);
Marshal.Copy(buff, ret, 0, objSize);
Marshal.FreeHGlobal(buff);
return ret;
}
}
}
Fixing Iran's Daylight Saving Time Information of Windows for the Next 100 Years
To make this article usable for the other countries as well, it's better to extract the time zone information from the modifier class.
ITimeZoneInformation
interface is a template for defining the time zone name, daylight start date and daylight end date.
using System;
namespace TimeZoneInfoEdit.Core.Contracts
{
public interface ITimeZoneInformation
{
string GetTimeZoneName();
DateTime GetDaylightDateTime(int year);
DateTime GetStandardDateTime(int year);
}
}
For example, here is the implantation of the ITimeZoneInformation
interface for Iran.
using System;
using System.
Globalization;
using TimeZoneInfoEdit.Core.Contracts;
namespace TimeZoneInfoEdit.Core
{
public class IranTimeZoneInformation : ITimeZoneInformation
{
public string GetTimeZoneName()
{
return "Iran Standard Time";
}
public DateTime GetDaylightDateTime(int year)
{
return new DateTime(getShamsiYear(year), 1, 1, 23, 59, 59, new PersianCalendar());
}
public DateTime GetStandardDateTime(int year)
{
return new DateTime(getShamsiYear(year), 6, 30, 23, 59, 59, new PersianCalendar());
}
static int getShamsiYear(int year)
{
return new PersianCalendar().GetYear(new DateTime(year, 9, 20, new GregorianCalendar()));
}
}
}
Now, we can inject this information into the ModifyTimeZoneInformation
class.
using System;
using System.Globalization;
using Microsoft.Win32;
using TimeZoneInfoEdit.Core.Contracts;
using TimeZoneInfoEdit.Core.Utils;
namespace TimeZoneInfoEdit.Core
{
public class ModifyTimeZoneInformation
{
private readonly ITimeZoneInformation _timeZoneInformation;
public ModifyTimeZoneInformation(ITimeZoneInformation timeZoneInformation)
{
_timeZoneInformation = timeZoneInformation;
}
public void Start()
{
writeSystemTimeZone();
modifyCurrentTzi();
fixDynamicDailylightSavingTime();
}
private void modifyCurrentTzi()
{
var tzi = getSystemTzi();
tzi.DaylightDate = _timeZoneInformation.GetDaylightDateTime(DateTime.Now.Year).ToSystemTime();
tzi.StandardDate = _timeZoneInformation.GetStandardDateTime(DateTime.Now.Year).ToSystemTime();
var timeZoneRegistryPath = TziConstants.CurrentVersionTimeZonesPath + _timeZoneInformation.GetTimeZoneName();
using (var key = Registry.LocalMachine.OpenSubKey(timeZoneRegistryPath, true))
{
var data = tzi.SerializeByteArray();
key.SetValue("TZI", data);
}
}
private void fixDynamicDailylightSavingTime()
{
var osVer = Environment.OSVersion.Version.ToString();
if (osVer.StartsWith("5"))
throw new InvalidOperationException
("Dynamic daylight Saving Time is not supported in Win-XP or 2003.");
var timeZoneRegistryPath = TziConstants.CurrentVersionTimeZonesPath +
_timeZoneInformation.GetTimeZoneName() + @"\Dynamic DST";
using (var key = Registry.LocalMachine.OpenSubKey(timeZoneRegistryPath, true))
{
var lastEntry = (int)key.GetValue("LastEntry");
var tzi = getSystemTzi();
int i;
for (i = lastEntry + 1; i < lastEntry + 100; i++)
{
tzi.DaylightDate = _timeZoneInformation.GetDaylightDateTime(i).ToSystemTime();
tzi.StandardDate = _timeZoneInformation.GetStandardDateTime(i).ToSystemTime();
var data = tzi.SerializeByteArray();
key.SetValue(i.ToString(CultureInfo.InvariantCulture), data, RegistryValueKind.Binary);
}
key.SetValue("LastEntry", i - 1, RegistryValueKind.DWord);
}
}
private Tzi getSystemTzi()
{
var timeZoneRegistryPath = TziConstants.LocalTimeZonesPath + _timeZoneInformation.GetTimeZoneName();
var tzi = (byte[])Registry.GetValue(timeZoneRegistryPath, "TZI", new byte[] { });
if (tzi == null || tzi.Length != 44)
throw new InvalidOperationException("Invalid TZI");
return tzi.DeserializeByteArray<Tzi>();
}
private void writeSystemTimeZone()
{
var date1 = _timeZoneInformation.GetDaylightDateTime
(DateTime.Now.Year).ToCustomSystemTime().SerializeByteArray();
var date2 = _timeZoneInformation.GetStandardDateTime
(DateTime.Now.Year).ToCustomSystemTime().SerializeByteArray();
using (var key = Registry.LocalMachine.OpenSubKey
(TziConstants.ControlTimeZoneInformationPath, true))
{
key.SetValue("DaylightStart", date1);
key.SetValue("StandardStart", date2);
}
}
}
}
ModifyTimeZoneInformation
class creates missing Dynamic daylight Saving Time information of the Windows registry, plus modifies the current time zone information.
To use this class, we can create a simple Console application as below:
using TimeZoneInfoEdit.Core;
namespace TimeZoneInfoEdit
{
class Program
{
static void Main(string[] args)
{
new ModifyTimeZoneInformation(new IranTimeZoneInformation()).Start();
}
}
}
After applying these changes which need the elevated privileges, it's necessary to restart the Windows.
References