【C#】端末のシステム日付を変更する

やはりエンジニアたるもの、出来る限り定時で帰りたいので、PCを起動する際にHTTP経由で毎日時間を合わせようと思います。

使用するWeb API

NICT(情報通信研究機構)が提供しているAPIを使用します。

タイムスタンプはどれでもどうにでもなるんですが、POSIXUNIX時間)にしますか。

システム日付の変更方法

Win32 APIのSetLocalTime関数を使用することで可能です。

MSDNを漁ってもいいんですが、ここに書いてある情報で十分です。

当然の如くUACが絡んでくるので、実行時には管理者権限を考慮する必要があります。

UNIX時間をDatetimeに変換する

これを参考にします。

要は1970年1月1日に経過秒数を足してやれば求めることが出来るので、そんなに難しくないです。

コード

ぴゃぴゃぴゃっと書いたので適当です。awaitが使えないとSystem.Net.Httpを使うメリットほとんどないですね…。

using System;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;

namespace SetSystemTime
{
    public class Program
    {
        private static DateTime _standardTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        [StructLayout(LayoutKind.Sequential)]
        public struct SystemTime
        {
            public ushort wYear;
            public ushort wMonth;
            public ushort wDayOfWeek;
            public ushort wDay;
            public ushort wHour;
            public ushort wMinute;
            public ushort wSecond;
            public ushort wMiliseconds;
        }

        [DllImport("kernel32.dll")]
        public static extern bool SetLocalTime(ref SystemTime sysTime);


        public static void Main(string[] args)
        {
            using (var client = new HttpClient())
            {
                var t = client.GetAsync("https://ntp-a1.nict.go.jp/cgi-bin/jst");

                if (!t.Wait(TimeSpan.FromSeconds(30)))
                {
                    Console.WriteLine("Timeout Error.");
                    return;
                }

                HttpResponseMessage resultMessage = null;
                try
                {
                    resultMessage = t.Result;
                }
                catch (Exception e)
                {
                    Console.WriteLine("Response Error:{0}", e.Message);
                    return;
                }

                using (resultMessage)
                {
                    if (!resultMessage.IsSuccessStatusCode)
                    {
                        Console.WriteLine("Http Error:{0}", resultMessage.StatusCode);
                        return;
                    }

                    var t2 = resultMessage.Content
                                 .ReadAsStringAsync();

                    t2.Wait();

                    //なんでこれHTMLで返ってくるんだろう…。
                    var result = t2.Result;
                    var regex = new Regex(@"<BODY>\n(\d+)\.\d+\n</BODY>", RegexOptions.Compiled);
                    long unixTime = 0;
                    if (!long.TryParse(regex.Match(result, 0).Groups[1].Value, out unixTime))
                    {
                        Console.WriteLine("Time Parse Error.");
                        return;
                    }

                    var currentTime = _standardTime.AddSeconds(unixTime).ToLocalTime();
                    var sysTime = ConvertSystemTime(currentTime);
                    SetLocalTime(ref sysTime);
                }
            }
        }

        public static SystemTime ConvertSystemTime(DateTime dt)
        {
            return new SystemTime
            {
                wYear = (ushort) dt.Year,
                wMonth = (ushort) dt.Month,
                wDay = (ushort) dt.Day,
                wHour = (ushort) dt.Hour,
                wMinute = (ushort) dt.Minute,
                wSecond = (ushort) dt.Second,
                wMiliseconds = (ushort) dt.Millisecond,
            };
        }
    }
}

デバッグも可能ですが、管理者権限で実行しないと実際に時間は変わりません。(特にエラーもでない)

まとめ

後はタスクスケジューラにでも放り込んでおけばOKです。

管理者権限で動かす方法はこの辺を読んでおけばいいと思います。

また、スケジューリングするならConsole.WriteLineで出してるログはテキストファイルか何かで出しておいたほうがいいと思います。まぁ、見たいなら、ですが。