2
\$\begingroup\$

I'm pulling the time and date from the internet because when the user turns the Raspberry Pi off, it loses its date and time settings and unfortunately Windows 10 IoT is very slow to correct it.

I have tried to use the DateTime method, however, I can't seem to work out how to create a Clock with a custom time and have it tick like as if i was to call DateTime.Now.ToShortTimeString(); (I'm not asking anyone to implement this).

Here is the following code that will scrap the information I need and parse it. The code works, however, sometimes there is lag (I'm guessing because the RPI isn't all that powerful) resulting in the time sometimes missing a second due to it trying to catch up I guess.

I'm not too worried about it being out by a couple of seconds however if you leave the program running on the Pi for a long period of time, it will end up being out by 20 mins to an hour depending on how long the application has been running.

Here is the code that I have created in a console application to make it a little more neater and to show where I'm guessing the problem is and if there is a better way to complete my task at hand.

class Program
{
    struct TimeAndDate
    {
        public int hour;
        public int minute;
        public int second;
        public DateTime date;
        public bool completed;
    }
    static TimeAndDate timeAndDate;
    static Clock clock;
    static void Main(string[] args)
    {
        getTime();
        Console.ReadLine();
        DateTime.Now.ToShortTimeString();
    }
    private static async void getTime()
    {
        Console.WriteLine("Entered getTime");
        string time = "";
        try
        {
            string pattern = @"<tr><td colspan=2 align=""center"" bgcolor=""#FFFFFF""><h5>([^>]*)</td></tr>";
            Task t = Task.Run(async () =>
            {
                string website = "http://www.worldtimezone.com/time/wtzresult.php?CiID=1225&forma=Find%20Time";
                using (HttpClient client = new HttpClient())
                using (HttpResponseMessage response = await client.GetAsync(website))
                using (HttpContent content = response.Content)
                {
                    string data = await content.ReadAsStringAsync();
                    foreach (Match match in (new Regex(pattern).Matches(data)))
                    {
                        time = match.Groups[1].Value;
                    }
                }
            });
            await t;
        }
        catch
        {
            Console.WriteLine("getTime Error");
            timeAndDate.completed = false;
        }
        DateTime datetime;
        if (DateTime.TryParse(time, out datetime))
        {
            timeAndDate.hour = datetime.Hour;
            timeAndDate.minute = datetime.Minute;
            timeAndDate.second = datetime.Second;

            Console.WriteLine(datetime.ToString("hh:mm:ss"));
            Console.WriteLine(datetime.Date.ToString("dd/MM/yyyy"));

            timeAndDate.completed = true;
            clock = new Clock(timeAndDate.hour, timeAndDate.minute, timeAndDate.second);
        while(true)
        {
            Console.WriteLine(clock.displayTime());
            await Task.Delay(1000);
            clock.Tick();
            //Console.Clear();
        }
        }
        else
        {
            Debug.WriteLine("Invalid Format");
            timeAndDate.date = DateTime.Now;
            timeAndDate.completed = false;
        }
    }
    class Clock
    {
        private int hour;
        private int minute;
        private int second;
        public Clock(int hh, int mm, int ss)
        {
            this.hour = hh;
            this.minute = mm;
            this.second = ss;
        }
        public void Tick()
        {
            this.second++;
            if(this.second == 60)
            {
                this.minute++;
                this.second = 00;
            }
            else if(this.minute == 60)
            {
                this.hour++;
                this.minute = 00;
            }
            else if(this.hour == 24)
            {
                this.hour = 00;
            }
        }
        public string displayTime()
        {
            return this.hour.ToString("D2") + ":" + this.minute.ToString("D2") + ":" + this.second.ToString("D2");
        }
    }
}

If worse comes to worse and I can't do much about it, I could always call the method needed every 10 or so minutes to correct the time. However, I was hoping I wouldn't need to do so.

\$\endgroup\$
  • \$\begingroup\$ The second paragraph is why I closed this, as it seems to involve code not yet implemented (unless you're not asking us how to do that). \$\endgroup\$ – Jamal Apr 30 '17 at 5:44
  • \$\begingroup\$ No, I was giving an example of what I thought i could do, and because i couldn't is one of the main reasons I have created the Clock class and why I have also added it to the question, sorry for the misunderstanding. \$\endgroup\$ – user1234433222 Apr 30 '17 at 5:46
  • \$\begingroup\$ Code indentation is broken and making it hard to read \$\endgroup\$ – Igor Soloydenko Apr 30 '17 at 6:24
  • \$\begingroup\$ I suppose there are big problem with correctness. The getTime is declared as an async void rather than async Task<T>. With the very first garbage collection, getTime will stop working even though there's a while (true) that does some console output... \$\endgroup\$ – Igor Soloydenko Apr 30 '17 at 6:31
3
\$\begingroup\$
foreach (Match match in (new Regex(pattern).Matches(data)))
{
    time = match.Groups[1].Value;
}

The first thing you should change is this loop. You don't need it as there is only one match on that page. Then you can use the static Regex.Match to find the data and you can use a named group instead of the 1 so this could be refactored to a method that actually does nothing else but returning the time string. It should not display the time yet. This means that the return type is not void any more (which should be used only for event handlers anyway) but a Task<string> (in case of a void it should just be Task).

private static async Task<string> GetTime()
{
    Console.WriteLine("Entered getTime");
    try
    {
        varpattern = @"<tr><td colspan=2 align=""center"" bgcolor=""#FFFFFF""><h5>(?<Data>[^>]*)</td></tr>";
        var getTime = Task.Run(async () =>
        {
            varwebsite = "http://www.worldtimezone.com/time/wtzresult.php?CiID=1225&forma=Find%20Time";
            using (var client = new HttpClient())
            using (var response = await client.GetAsync(website))
            using (var content = response.Content)
            {
                string data = await content.ReadAsStringAsync();
                return Regex.Match(data, pattern).Groups["Data"].Value;
            }
        });
        return await getTime;
    }
    catch (Exception ex)
    {
        Console.WriteLine($"getTime Error: {ex.ToString()}");
        return null;
    }
}

The rest of it should be implemented in the InitializeClock method that now requires a startTime and creates an instance of a Clock if everything went fine:

private static void InitializeClock(string startTime)
{
    DateTime datetime;
    if (DateTime.TryParse(startTime, out datetime))
    {
        Console.WriteLine(datetime.ToString("hh:mm:ss"));
        Console.WriteLine(datetime.Date.ToString("dd/MM/yyyy"));
        clock = new Clock(datetime);
    }
    else
    {
        Debug.WriteLine("Invalid Format");
    }
}

You use the Date property to format the date. This is not necessary and you can format any part of a date time with the appropriate format string, see String Format for DateTime for many more examples.


The last change is inside the clock. Instead of running a while(true) loop you should use a Timer that will tick every second and you can either count the seconds with a counter (like I did) or modify the DateTime field. You can do this with the AddSeconds method which returns a new instance of the DateTime struct so you'll need to save this result or like I did just display the new time with some formatting.

This is just an example and the clock should not actually write to the console itself. You might want to pass it a service via DI or you might have an entirely different idea depending on your actual solution.

class Clock
{
    private readonly DateTime _startTime;
    private double _offset;
    private readonly System.Timers.Timer _timer;
    public Clock(DateTime startTime)
    {
        _startTime = startTime;
        _timer = new System.Timers.Timer(1000);
        _timer.Elapsed += (sender, e) =>
        {
            _offset++;
            DisplayTime();
        };
        _timer.Start();
    }

    public void DisplayTime()
    {

        Console.WriteLine(_startTime.AddSeconds(_offset).ToString("hh:mm:ss"));
    }
}

In this example I'm using an anonymous function for the event handler. There are also other techniques. You can read more about them on How to: Subscribe to and Unsubscribe from Events .


The new Main would then just pass the time string to the InitializeClock method but since GetTime is async and Main is not we need to use Task.Run to be able to await it:

Task.Run(async () =>
{
    var time = await GetTime();
    InitializeClock(time);
});

Now you don't need any additional data structures like the TimeAndDate and splitting the time into pieces and calculate the hour or minute part yourself.

\$\endgroup\$
  • \$\begingroup\$ Good job! My first attempt resembles what you've posted. I decided not to share my code originally because I felt I am missing something in the requirements or even deeply misunderstand them. The most concerning thing to me is the "half-baked" original code that doesn't do one thing that it promises: namely, prevents accumulated time error over long program run periods. I mean, the time is only synched with the remote service once, at startup. Number two is lack of the actual code that does some meaningful job. I am very positive that the actual time is used somewhere else, but where? \$\endgroup\$ – Igor Soloydenko Apr 30 '17 at 19:05
  • \$\begingroup\$ quick question @t3chb0t if you don't mind? with the clock from what I can tell, are you turning the ticks into a time format? also with the last block of code, how are you able to run the Task.Run(async () => InitializeClock(await time)); I thought you could only do this with another Task also _timer.Elapsed += (sender, e) => { _offset++; DisplayTime(); }; normally from what I have done, this would be an event with a method, is it basically the same but quicker? whats this type of pattern called so I can do some research? Thank you again \$\endgroup\$ – user1234433222 Apr 30 '17 at 23:06
  • \$\begingroup\$ @Werdna the answer woulnd't fit into a comment so I've improved the review and added a few comments and links that you might find helpful here and there. I've also changed the Task.Run part for the main so it's more obvious what's going on there. \$\endgroup\$ – t3chb0t May 1 '17 at 6:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.