My times, they are changing, that is, because I need them to. I am testing some cases involving a scheduler I use and this involves behavior around transitions to and from daylight saving time.
From this post I got a working method that enables me to change the system date programmatically (reposting most of the code):
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEMTIME
{
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetSystemTime(ref SYSTEMTIME st);
and for my own convenience I am just wrapping that in this function that I actually call:
public static void SetSytemDateTime(DateTime timeToSet)
{
DateTime uniTime = timeToSet.ToUniversalTime();
SYSTEMTIME setTime = new SYSTEMTIME()
{
wYear = (short)uniTime.Year,
wMonth = (short)uniTime.Month,
wDay = (short)uniTime.Day,
wHour = (short)uniTime.Hour,
wMinute = (short)uniTime.Minute,
wSecond = (short)uniTime.Second,
wMilliseconds = (short)uniTime.Millisecond
};
SetSystemTime(ref setTime);
}
The additional conversion to Universal Time is necessary, otherwise I don't get to see the date I passed to the method in my clock (down in the task bar).
Now this works fine considering this code for example:
DateTime timeToSet = new DateTime(2014, 3, 10, 1, 59, 59, 0);
Console.WriteLine("Attemting to set time to {0}", timeToSet);
SetSytemDateTime(timeToSet);
Console.WriteLine("Now time is {0}, which is {1} (UTC)", DateTime.Now, DateTime.UtcNow);
Thread.Sleep(TimeSpan.FromSeconds(5));
DateTime actualSystemTime = GetNetworkTime();
SetSytemDateTime(actualSystemTime);
The method GetNetworkTime
is actually just grabbed from over here, so I can set my clock back to the "real" time after testing, you can ignore it for this question's sake.
That does, what you'd expect (German DateTime formatting, don't get confused):
And in the task bar I also see what I expect:
But now to the weird part: Switch the first line of the calling code for
// one second before transition to daylight saving time in Berlin
DateTime timeToSet = new DateTime(2015, 3, 29, 1, 59, 59, 0);
Now the command line output actually seems to satisfy what we'd expect to see:
But then we take a look down to the right of our task bar and enter frowny land and see a time that should actually not exist for that day:
Now, the funny thing is, when I try the same thing for the second before the transition out of daylight saving time, the change gets "accepted" (switching first calling code line again):
// one second before transition out of daylight saving time in Berlin
DateTime timeToSet = new DateTime(2014, 10, 26, 2, 59, 59, 0);
We see what we'd expect in the command line output:
also in the task bar clock:
But this story also has a sad ending, let one second pass and you would expect the clock to show 2 'o clock, but instead:
Which is a time that should actually occur one hour later on that particular day (if you switch the time manually in windows this transitions as expected).
Now, what am I missing here, why can't I target the second before transition to daylight saving time and why don't I see the transition out of daylight saving time when I do the DateTime-changes programmatically this way?
What do I need to add/set so I can?
What Andrew Morton and Marc proposed was spot on!
Though I must say I still don't understand why I would not be able to achieve the same thing using SetSystemTime (Doing conversions to universial time of course), it really DOES work using SetLocalTime.
Please upvote Marc's post as well, I am just writing this so there is a complete code example to demonstrate how the tests would look if they run successfully.
This code runs 3 tests:
(Posting a complete working example, but do note to reproduce this on your system you might have to use different DateTime-Values, due to the daylight saving time transition in your time zone [if you're not working in the timezone of Berlin], and also you might have to [or just like to] use another NTP-server in GetNetworkTime()
)
// complete example use this as Program.cs in a console application project
namespace SystemDateManipulator101
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Program class.
/// </summary>
public class Program
{
#region Methods
static void Main(string[] args)
{
// test one: set system time to a random time that is not near daylight savings time transition
DateTime timeToSet = new DateTime(2014, 5, 5, 4, 59, 59, 0);
Console.WriteLine("timeToSet Kind: {0}", timeToSet.Kind);
Console.WriteLine("Attemting to set time to {0}", timeToSet);
SetLocalSytemDateTime(timeToSet);
Console.WriteLine("Now time is {0}, which is {1} (UTC)", DateTime.Now, DateTime.UtcNow);
Thread.Sleep(TimeSpan.FromSeconds(5));
DateTime actualSystemTime = GetNetworkTime();
SetLocalSytemDateTime(actualSystemTime);
Thread.Sleep(TimeSpan.FromSeconds(5));
// test two: set system time to one second before transition to daylight savings time in Berlin
timeToSet = new DateTime(2015, 3, 29, 1, 59, 59, 0);
Console.WriteLine("timeToSet Kind: {0}", timeToSet.Kind);
Console.WriteLine("Attemting to set time to {0}", timeToSet);
SetLocalSytemDateTime(timeToSet);
Console.WriteLine("Now time is {0}, which is {1} (UTC)", DateTime.Now, DateTime.UtcNow);
Thread.Sleep(TimeSpan.FromSeconds(5));
actualSystemTime = GetNetworkTime();
SetLocalSytemDateTime(actualSystemTime);
Thread.Sleep(TimeSpan.FromSeconds(5));
// test three: set system time to one second before transition out of daylight savings time in Berlin
timeToSet = new DateTime(2014, 10, 26, 2, 59, 59, 0);
Console.WriteLine("timeToSet Kind: {0}", timeToSet.Kind);
Console.WriteLine("Attemting to set time to {0}", timeToSet);
SetLocalSytemDateTime(timeToSet);
Console.WriteLine("Now time is {0}, which is {1} (UTC)", DateTime.Now, DateTime.UtcNow);
Thread.Sleep(TimeSpan.FromSeconds(5));
actualSystemTime = GetNetworkTime();
SetLocalSytemDateTime(actualSystemTime);
Console.Read();
}
#endregion
// https://stackoverflow.com/a/12150289/162671
public static DateTime GetNetworkTime()
{
//default Windows time server
const string ntpServer = "time.windows.com";
// NTP message size - 16 bytes of the digest (RFC 2030)
var ntpData = new byte[48];
//Setting the Leap Indicator, Version Number and Mode values
ntpData[0] = 0x1B; //LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode)
var addresses = Dns.GetHostEntry(ntpServer).AddressList;
//The UDP port number assigned to NTP is 123
var ipEndPoint = new IPEndPoint(addresses[0], 123);
//NTP uses UDP
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.Connect(ipEndPoint);
//Stops code hang if NTP is blocked
socket.ReceiveTimeout = 3000;
socket.Send(ntpData);
socket.Receive(ntpData);
socket.Close();
//Offset to get to the "Transmit Timestamp" field (time at which the reply
//departed the server for the client, in 64-bit timestamp format."
const byte serverReplyTime = 40;
//Get the seconds part
ulong intPart = BitConverter.ToUInt32(ntpData, serverReplyTime);
//Get the seconds fraction
ulong fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4);
//Convert From big-endian to little-endian
intPart = SwapEndianness(intPart);
fractPart = SwapEndianness(fractPart);
var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);
//**UTC** time
var networkDateTime = (new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)).AddMilliseconds((long)milliseconds);
return networkDateTime.ToLocalTime();
}
// stackoverflow.com/a/3294698/162671
static uint SwapEndianness(ulong x)
{
return (uint)(((x & 0x000000ff) << 24) +
((x & 0x0000ff00) << 8) +
((x & 0x00ff0000) >> 8) +
((x & 0xff000000) >> 24));
}
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEMTIME
{
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetSystemTime(ref SYSTEMTIME st);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetLocalTime(ref SYSTEMTIME st);
public static void SetSystemDateTime(DateTime timeToSet)
{
DateTime uniTime = timeToSet.ToUniversalTime();
SYSTEMTIME setTime = new SYSTEMTIME()
{
wYear = (short)uniTime.Year,
wMonth = (short)uniTime.Month,
wDay = (short)uniTime.Day,
wHour = (short)uniTime.Hour,
wMinute = (short)uniTime.Minute,
wSecond = (short)uniTime.Second,
wMilliseconds = (short)uniTime.Millisecond
};
SetSystemTime(ref setTime);
}
public static void SetLocalSytemDateTime(DateTime timeToSet)
{
SYSTEMTIME setTime = new SYSTEMTIME()
{
wYear = (short)timeToSet.Year,
wMonth = (short)timeToSet.Month,
wDay = (short)timeToSet.Day,
wHour = (short)timeToSet.Hour,
wMinute = (short)timeToSet.Minute,
wSecond = (short)timeToSet.Second,
wMilliseconds = (short)timeToSet.Millisecond
};
SetLocalTime(ref setTime);
// yes this second call is really necessary, because the system uses the daylight saving time setting of the current time, not the new time you are setting
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms724936%28v=vs.85%29.aspx
SetLocalTime(ref setTime);
}
}
}
If you want to experience the weirdness I described in my question, you still can, just replace the calls to SetLocalSytemDateTime
by SetSytemDateTime
.