I have an application that loops reading a big text file and runs for at least 48 hours.
I would like to stop its execution from 23.00 to 08.00 (11pm to 8am) and then continue its loop during the day.
I tried the following code, but it doesn't exit from the loop.
Edit after Dave Cross' reply
my $min = (localtime)[1];
my $hour = (localtime)[2];
while ( $hour > 18 and $min > 0 ) {
sleep(1);
$hour = (localtime)[2];
$min = (localtime)[1];
}
This doesn't work as expected. Now it's 18:48 and the loop exits immediately unless I use one hour before:
while ( $hour > 17 and $min > 0 )
You need to check the time more than once :-)
And rather than calling strftime(), why not just use the return value from localtime()?
my $hour = (localtime)[2];
while ($hour >= 23 or $hour < 8) {
sleep(1);
$hour = (localtime)[2];
}
Update: A more efficient approach might be to work out how long to sleep() in order to wake up at 8am. A naive approach would be something like this:
if ((localtime)[2]) >= 23) {
sleep(9 * 60 * 60); # sleep for nine hours
}
Related
I am trying to set up a timestamp range for a file that is not over 2 hours in Perl. Taking times from file are formatted as "May 26 20:30 filename.csv" in perl format.
Here is what I am setting up:
use File::stat;
my $stat = stat($file);
# To start a time within a range or no more than 2 hours
my $start = UnixDate("now", "%m%d %H:%M");
my $stop = UnixDate("$start"+2, "%m%d %H:%M"); // here max it to 2 hours, not sure this part
if ($stat->$start <= $stat->$stop) { next; }
Since you say that UnixDate returns an epoch timestamp, all you need is this:
my $cutoff = time - 2*60*60;
my $file_time = UnixDate( "%m%d %H:%M", $fn );
next if $file_time < $cutoff;
I am using Perl for time comparison.
#!/usr/bin/perl
print $fh "End Time : $endTime ";
print $fh "Before if loop : ", time();
# time() gives current time timestamp
if ( time() < $endTime ) {
print $fh "Inside if loop : ", time();
$exitValue = 11;
}
Currently, i am using if ( time() < $endTime ). Here, time() gives timestamp in millisecomds but i get $endTime as timestamp in minutes.
I want to compare timestamp in munutes.
How do i get timestamp in minutes?
time() gives you the time since the epoch in seconds.
If you want to have minutes, you can use this:
my $time_min = int(time() / 60);
David Cross justly emphasizes that using int() always rounds down. Alternatives include:
my $time_sec = sprintf '%.0f', time() / 60; (Note: uses "half-to-even" rounding)
use Math::Round
as FSp suggested, use POSIX' floor() or ceil()
I see two possible solutions:
you can multiply $endTime by the milliseconds in a minute (i.e. $endTime = $endTime * 60000) and later compare it with time() as you are doing
you can take the integer division of time() by the milliseconds in a minute (i.e. if (floor(time() / 60000) < $endTime) ...). As long as I know, you need to use POSIX in order to use floor
The first would be my favourite option.
It may be a simple question for a perl expert -
I am trying to run a script which should logically do this>
Main
{
While (true)
Call Sub A
Call Sub B
Call Sub C
}
Sub A
{
Go execute a method A for whatever it needs to do ---
before next time method A runs it need to wait atleast 60 seconds but i still want to call method B in these 60 seconds.
}
Sub B
{
Go execute a method B for whatever it needs to do ---
before next time method b runs it need to wait atleast 60 seconds but i still want to call method C in these 60 seconds in mean time.
}
Sub C
{
Go execute a method C for whatever it needs to do ---
before next time method C runs it need to wait atleast 60 seconds at this moment control should be back in Main and wait for first 60 seconds of A to expire so that Sub A can be called
}
My question:
Q: what is best and optimized way can i do this -?
Q: If i put sleep 60 in each sub then next sub will not be called even till 60 seconds expire and it will delay overall processing.
Q: I want 3 subs called every 60 seconds in a sequential order
Q Lastly if i need to call 2 subs in every 60 seconds and last sub every hour - how do i do it?
Comment - my thought was to take UTC as a variable and store it in a variable and keep checking time if time expires than call individual subs but not sure if it optimum way of running code.
Inside the subroutine record the time when it last ran, then wait if necessary on the next execution:
use feature 'state';
my $sleep_duration = 60; # seconds
sub foo {
state $last_execution = 0;
my $remaining = $last_execution + $sleep_duration - time;
sleep $remaining if $remaining > 0;
$last_execution = time;
...; # normal processing for this sub
}
You probably want to use threads. The following snippet might give you a starting point. Since you have four Qs instead of one, I won't go into detail for each of them.
#!/usr/bin/perl
use warnings;
use strict;
use threads;
my #t = (
threads->create(\&A),
threads->create(\&B),
threads->create(\&C)
);
$t[0] -> join();
$t[1] -> join();
$t[2] -> join();
sub A {
for my $i (1 .. 10) {
print "A $i\n";
sleep 60;
}
}
sub B {
for my $i (1 .. 20) {
print "B $i\n";
sleep 60;
}
}
sub C {
for my $i (1 .. 3) {
print "C $i\n";
sleep 60;
}
}
Using Amon idea, but with sleeping logic outside functions,
sub get_sleeper {
my ($sleep_duration) = #_;
my $last_execution = 0;
return sub {
my $remaining = $last_execution + $sleep_duration - time();
sleep $remaining if $remaining >0;
$last_execution = time();
};
}
my ($sleepA, $sleepB, $sleepC) = map get_sleeper(60), 1..3;
while (1) {
$sleepA->(); SubA();
$sleepB->(); SubB();
$sleepC->(); SubC();
}
Both answers are good but a sort of middle way would be: (stolen from the answer by amon)
use feature 'state';
my $sleep_duration = 60; # seconds
sub foo {
state $last_execution = 0;
my $remaining = $last_execution + $sleep_duration - time;
if ($remaining > 0)
{
return "Waiting"; # Make sure main checks for this to know whether the sub ran.
}
...; # normal processing for this sub
}
This will allow you to run the next sub while this one wait without needing more than one thread. But of course, it means that this sub will run only after the other two had their chance. This means you mix up the order and depending on subs, take longer than 60 seconds till the next call.
I searched around for this a lot, and it appears all solutions are making use of some module or the other.
I have 2 dates in yyyymmdd format. I'd like to know of a simple way to calculate the number of days between these dates, without using any modules.
Example:
Date 1: 20130625
Date 2: 20130705
Number of days = 10
PS: I cannot use any modules due to restrictions on the server I will be running the script on. Perl version - 5.8.4
Expression returns 10.
(
Time::Piece->strptime('20130705', '%Y%m%d')
- Time::Piece->strptime('20130625', '%Y%m%d')
)->days
Time::Piece is part of the core Perl distribution since v5.9.5.
sub yyyymmdd_to_rata_die {
use integer;
my ( $y, $m, $d ) = $_[0] =~ /\A([0-9]{4})([0-9]{2})([0-9]{2})\z/
or return;
my $adj;
# make month in range 3..14 (treat Jan & Feb as months 13..14 of prev year)
if ( $m <= 2 ) {
$y -= ( $adj = ( 14 - $m ) / 12 );
$m += 12 * $adj;
}
elsif ( $m > 14 ) {
$y += ( $adj = ( $m - 3 ) / 12 );
$m -= 12 * $adj;
}
# add: day of month, days of previous 0-11 month period that began w/March,
# days of previous 0-399 year period that began w/March of a 400-multiple
# year), days of any 400-year periods before that, and 306 days to adjust
# from Mar 1, year 0-relative to Jan 1, year 1-relative (whew)
$d += ( $m * 367 - 1094 ) / 12 + $y % 100 * 1461 / 4 + ( $y / 100 * 36524 + $y / 400 ) - 306;
}
print yyyymmdd_to_rata_die(20130705) - yyyymmdd_to_rata_die(20130625);
Its simple:
1) for each date:
1.1) turn years into ydays
1.2) turn months into mdays
1.3) make sum days + mdays + ydays
2) substract that two values
How can I tell Perl to run some code every 20 seconds?
for (;;) {
my $start = time;
# your code;
if ((my $remaining = 20 - (time - $start)) > 0) {
sleep $remaining;
}
}
while (1) {
sleep 20;
<your code here>;
}
Set up a SIGALRM handler, and send yourself a signal every 20 seconds with alarm (see perldoc -f alarm):
$SIG{ALRM} = sub {
# set up the next signal for 20 seconds from now
alarm 20;
# do whatever needs to be done
# ...
};
This will experience drift over time, as each signal may be delayed by up to a second; if this is important, set up a cron job instead. Additionally, even more drift will happen if your other code takes upwards of 20 seconds to run, as only one timer can be counting at once. You could get around this by spawning off threads, but at this point, I would have already gone back to a simple cron solution.
Choosing the proper solution is dependent on what sort of task you need to execute periodically, which you did not specify.
While the sleep function will work for some uses, if you're trying to do "every 20 seconds, forever", then you're better off using an external utility like cron.
In addition to the possible issue of drift already mentioned, if your sleep script exits (expectedly or otherwise), then it's not going to run again at the next 20 second mark.
#Blrfl is correct, and I feel sheepish. That said, it's easy enough to overcome.
* * * * * /path/to/script.pl
* * * * * sleep 20 && /path/to/script.pl
* * * * * sleep 40 && /path/to/script.pl
You could also take a hybrid approach of putting a limited count sleep loop in the script and using cron to run it every X minutes, covering the case of script death. Anything more frequent than 20 seconds, I would definitely take that approach.
See Schedule::ByClock:
#!/usr/bin/perl
use strict; use warnings;
use Schedule::ByClock;
my $scheduler = Schedule::ByClock->new(0, 20, 40);
while ( defined( my $r = $scheduler->get_control_on_second ) ) {
printf "%02d\n", $r;
}
All the caveats others pointed out still apply, but I think the module is neat.
#!/usr/bin/perl -w
use strict;
# heartbeat
$SIG{ALRM} = sub {
# set up the next signal for 20 second from now
alarm(20);
printf "<heartbeat/>\n";
};
alarm(20); # start timer
my $i = 0;
while(1) {
$i++;
printf "loop" . $i . "\n";
sleep 2;
}
alarm(0);
printf "done!\n";
Output :
loop1
loop2
loop3
loop4
loop5
loop6
loop7
loop8
loop9
loop10
<heartbeat/>
loop11
loop12
loop13
loop14
loop15
loop16
loop17
loop18
loop19
loop20
<heartbeat/>
It is better to use some event library. There are a couple of choices:
IO::Async::Timer::Periodic
use IO::Async::Timer::Periodic;
use IO::Async::Loop;
my $loop = IO::Async::Loop->new;
my $timer = IO::Async::Timer::Periodic->new(
interval => 60,
on_tick => sub {
print "You've had a minute\n";
},
);
$timer->start;
$loop->add( $timer );
$loop->run;
AnyEvent::DateTime::Cron
AnyEvent::DateTime::Cron->new()
->add(
'* * * * *' => sub { warn "Every minute"},
'*/2 * * * *' => sub { warn "Every second minute"},
)
->start
->recv
EV::timer
etc.
Go with ..
while () {
sleep 20;
#Code here
}