http://www.perlmonks.org?node_id=1176366

stevieb has asked for the wisdom of the Perl Monks concerning the following question:

This is more a theoretical question than one of problematic code, but I've been fighting with this off and on for a few days now so I seek advice.

I have an event loop running in a separate process every three seconds. This event decides to turn a light on at a specific time of day, and then turns it off X hours later.

What I need to do is the following. Let's use 2000 (8pm) for lights on, and 12 for the number of hours the lamp should remain on:

Because on/off can cross the midnight boundary, calculating by hour/minute alone doesn't work correctly, reliably.

I have a DB backend that I can store epoch in for when lights-on is to happen, as well as for lights-off. I can also create two class variables to store those DateTime objects so I'm not recreating them every three seconds.

I've tried all manner of things, but either accuracy or performance suffers, and I just can't get it right.

I'm thinking something along the lines of the following pseudo code, but am wondering if someone can suggest alternate/better ideas.

- global on_time datetime - global off_time datetime - if now > on_time && now < off_time -- if light is not on ---- turn light on - else -- if light is on ---- turn light off

What I'm for some reason struggling with, is re-setting the lights-on DB entry/datetime when midnight hits (so that it reflects the current day). If I do it in a separate event, that can and will throw off the next check on the 3 second event. I can't do it in lights-off routine, because if lights run from say 0600-1800, the clock will be set back to 0600 for current day, and won't go on tomorrow. I can't set it in the lights-on routine, for similar reasons. I know I could add a day depending on the time of day, but that's too much work and prone for error if the lights-on time changes.

Perhaps one way would be to check the current time in the lights-off routine as it's about to turn the light off, and if its before midnight, add a day to the next lights-on dt, but I'm afraid I'm way overthinking things here.

Note: the light will always only have one on period within each 24 hour period (regardless of when it is within that window).

Replies are listed 'Best First'.
Re: Logical ways to calculate being within two times
by BrowserUk (Patriarch) on Nov 22, 2016 at 22:05 UTC

    Why not convert your start times to epoch time and calculate an end time as an epoch time, when you receive the start/duration pair. After that, all the problems of midnight disappear.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority". The enemy of (IT) success is complexity.
    In the absence of evidence, opinion is indistinguishable from prejudice.

      If I'm understanding you correctly, I really like this idea, as doing the math on the epoch directly, I wouldn't even need to create DateTime objects at all.

      I'll definitely put some thought into how I can make all the pieces fit using this idea.

      Cheers

      -stevieb

        If I'm understanding you correctly,

        Given your 20:00 start time and 12 hours duration, calculate epoch times:

        $s = '20:00'; $d = 12; $e = time; $e += 60 until localtime( $e ) =~ m[$s:]; $start = $e; $end = $start + $d * 3600; printf "start:%f end:%f\n", $start, $end;; start:1479931244.206990 end:1479974444.206990

        The code above assumes that if a start time is given that is earlier than the current time, it means that time tomorrow.

        You might be concerned by the 'crude' mechanism of converting the time to an epoch -- the until loop -- but it will take at most 1/10,000th of a second to do it.

        It also assumes that time will be given to the nearest minute; though it is easily changed to allow to the nearest second, but the epoch conversion would then take upto 5/10,000ths of a second.

        Of course, you could also load the whole of DateTime to do the conversion, but that probably takes longer just to load than this method :)


        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority". The enemy of (IT) success is complexity.
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Logical ways to calculate being within two times
by BillKSmith (Monsignor) on Nov 22, 2016 at 22:46 UTC
    There probably is no fully general solution using only time-of-day. BrowserUK suggests a simple way to exploit date information.
    Bill

      Yeah, as I said in the OP, I honestly felt I was/am way over-thinking this. To be honest, I was typing out my question all the while hoping it would be another case of Rubber Duck Debugging, but alas... even after edits and code testing, it didn't come to me, so I hit submit ;)

Re: Logical ways to calculate being within two times
by stevieb (Canon) on Nov 27, 2016 at 17:09 UTC

    For completeness, and for a bit of content on the typically lower volume Sunday...

    Set the time, called only once, upon initial loading of the webapp:

    $api->set_light_times;

    Sets the light timers... once on initialization of the app as a whole, and then again every time the light-off is triggered:

    sub set_light_times { my ($self) = @_; my $on_at = $self->_config_light('on_at'); my $time = time; $time += 60 until localtime($time) =~ /$on_at:/; my $hrs = $self->_config_light('on_hours'); my $on_time = $time; my $off_time = $on_time + $hrs * 3600; my $now = time; # check if the until() loop set things beyond today's # on period if ($now > ($on_time - 86400) && $now < ($off_time - 86400)){ $on_time -= 24 * 3600; $off_time -= 24 * 3600; } $self->db()->update( 'light', 'value', $on_time, 'id', 'on_time' ); $self->db()->update( 'light', 'value', $off_time, 'id', 'off_time' ); }

    ...and the action logic itself:

    sub action_light { my ($self) = @_; my $log = $log->child('action_light'); my $on_hours = $self->_config_light('on_hours'); my $aux = $self->_config_control('light_aux'); my $pin = $self->aux_pin($aux); my $on_time = $self->_config_light('on_time'); my $off_time = $self->_config_light('off_time'); my $now = time; if (($on_hours == 24) || ($now > $on_time && $now < $off_time)){ if (! $self->aux_state($aux)){ $self->aux_state($aux, ON); pin_mode($pin, OUTPUT); write_pin($pin, HIGH); } } elsif ($self->aux_state($aux)){ $self->aux_state($aux, OFF); pin_mode($pin, OUTPUT); write_pin($pin, LOW); $self->set_light_times; } }

      One possible caveat of the epoch time calculation.

      If you set a time that happens to coincide to the minute, with the subtraction of a leap second, then the until loop may never terminate.

      The solution is to add 30 rather than 60 each iteration.

      Of course, if your processor doesn't use NTP or some similar method to synchronise its system clock, the problem is probably moot.


      With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority". The enemy of (IT) success is complexity.
      In the absence of evidence, opinion is indistinguishable from prejudice.

        My experience says that leap seconds do not impact epoch time (note that after inserting 26 leap seconds, the epoch time at the top of a minute is still currently a multiple of 60, whether using UTC or not). And, when you think about it, they can't.

        Leap seconds are not scheduled very far in advance but epoch times for years in the future have been being used for years in the past. So leap seconds can't impact (Unix-style) epoch time values else past calculations of future dates would become inaccurate.

        - tye        

      To guard against the infinite loop / leapsecond / 59 second minute

      my $failsafe = 0; $time += 60 until localtime($time) =~ /$on_at:/ or $failsafe++ > +10_000;

      Adjust the failsafe cutoff until its just large enough but not too large :) If it slows down this approach change it :) maybe use strptime

        That breaks the loop, but doesn't fix up the problem.

        Adding 30 instead of 60 prevents the problem completely and runs more quickly.


        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority". The enemy of (IT) success is complexity.
        In the absence of evidence, opinion is indistinguishable from prejudice.