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

I've been experiencing some trouble with some clock timing code and its associated code that sets the times in my indoor grow room automation software, and am looking for some assistance in completely re-writing it.

I have to admit that what I thought was going to be a simple task ended up (at least to me) not being simple at all. The below attempt at the refactor uses DateTime objects to perform the math. It seems like it has to do a lot of expensive work, and I'm hoping the Monks can point out and/or help me devise something that's more efficient in terms of the work being done. Note that the light timer routine is run every 15 seconds in an async event, so it most definitely is not just a one-off process.

An example of what I'm trying to achieve:

One problem I experienced with my existing code was that I was searching for the on-time with a regex using localtime(): if (localtime(time) =~ /$on_time:/){...}. If the power went out and came back on say at 1805 hrs, the light would not trigger, nor the future date would be set. This is a significant and potentially fatal problem for the plants (it can cause the females enough stress to go hermaphrodite) if this isn't noticed within a day or two. This new code does not check the time in the same way, and it eliminates this (single) problem.

Am I way overthinking this here? Perhaps I've been staring at it for too long and am blinded to something simply obvious. Or, maybe not, and this is a good way to achieve what I want and there's just a more efficient and less costly way. Or perhaps it's perfect, and I shouldn't change anything. I have laid out the code so it doesn't wrap here on PM, and added comments in hopes to make it easier to follow as it's not just a simple code block.


use warnings; use strict; use feature 'say'; use DateTime; # this is a prototype of a light timer my $on_at = '18:00'; # lights on my $on_time = 12; # hours on for my $off_time; my $on = 0; # light is on bool my $t = time; my $count = 0; # iter count my $done = 20000; # num iterations # dt objects my ($t1, $on_at_dt, $off_at_dt) = init_time($on_at, $on_time); while (1){ # on if ($t1 > $on_at_dt && ! $on){ # light is not on, and it's time $on = 1; say "\non: $t1"; } # off if ($t1 > $off_at_dt && $on){ # light's out time $on = 0; say "off: $t1"; $on_at_dt = set_on_timer( $t1, $on_at_dt, $on_at ); $off_at_dt = set_off_timer( $on_at_dt, $off_at_dt, $on_time ); } # expedite the current time for testing $t1->add(seconds => 30); $count++; last if $count == $done; } sub init_time { my ($on_at, $on_time) = @_; my $t1 = DateTime->now; $t1->set_time_zone('local'); my $on_at_dt = $t1->clone; $on_at_dt->set_hour( (split(/:/, $on_at))[0] ); $on_at_dt->set_minute( (split(/:/, $on_at))[1] ); my $off_at_dt = $on_at_dt->clone; $off_at_dt->add(hours => $on_time); return ($t1, $on_at_dt, $off_at_dt); } sub set_off_timer { my ($on_dt, $off_dt, $on_time) = @_; $off_dt = $on_dt->clone; $off_dt->add(hours => $on_time); return $off_dt; } sub set_on_timer { my ($t1, $dt, $on_at) = @_; $dt = $t1->clone; $dt->set_second(0); $dt->set_hour((split(/:/, $on_at))[0]); $dt->set_minute((split(/:/, $on_at))[1]); return $dt; }

Here are a couple of benchmarks I put together to try to get it to be as efficient as I could make it.