Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / Win32
Tip/Trick

Fixing Daylight Saving Time of Windows for Next 100 years

Rate me:
Please Sign up or sign in to vote.
4.94/5 (7 votes)
30 Sep 2014CPOL2 min read 31.6K   204   7   4
Microsoft has not updated Iran's daylight saving time information since 2009. Let's find out how it works and then fix it for the next 100 years!

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:

Image 1

Image 2

Image 3

[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:

ASP.NET
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:

C#
using System.Runtime.InteropServices;

namespace TimeZoneInfoEdit.Core.Contracts
{
    /// <summary>
    /// Time zone information
    /// </summary>
    [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:

C#
using System.Runtime.InteropServices;

namespace TimeZoneInfoEdit.Core.Contracts
{
    /// <summary>
    /// Represents the SystemTime structure of SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones.
    /// Its DayOfWeek is not located at the end.
    /// </summary>
    [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:

C#
public static SystemTime ToSystemTime(DateTime time)
{
      var result = new SystemTime
      {
          Year = 0, // relative time
          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; // it's number of the week and not number of the day
      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:

C#
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.

C#
using System; 

namespace TimeZoneInfoEdit.Core.Contracts
{
    /// <summary>
    /// Represents the general time zone information of a country.
    /// </summary>
    public interface ITimeZoneInformation
    {
        /// <summary>
        /// Time zone name
        /// </summary>        
        string GetTimeZoneName();

        /// <summary>
        /// Daylight start date
        /// </summary>
        /// <param name="year">Gregorian Year</param>        
        DateTime GetDaylightDateTime(int year);

        /// <summary>
        /// Daylight end date
        /// </summary>
        /// <param name="year">Gregorian Year</param>        
        DateTime GetStandardDateTime(int year);
    }
}

For example, here is the implantation of the ITimeZoneInformation interface for Iran.

C#
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.

C#
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:

C#
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Iran (Islamic Republic of) Iran (Islamic Republic of)
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionHex value for 4 Daylight Savings Time in Casablanca time zone Pin
Member 1309132528-Mar-17 20:21
Member 1309132528-Mar-17 20:21 
GeneralMy vote of 5! Pin
jediYL27-Oct-14 14:26
professionaljediYL27-Oct-14 14:26 
QuestionHow to execute these codes? Pin
Rami Alhasani12-Oct-14 2:19
Rami Alhasani12-Oct-14 2:19 
AnswerRe: How to execute these codes? Pin
Vahid_N12-Oct-14 4:17
Vahid_N12-Oct-14 4:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.