How to identify a date for the row of each days? - arrays

Please, have a look!
I have number of days from 1 to 30 days, so I need to loop through number of days and identify a date for each correspondings days with one "for" loop, The started date is: $epoc = 2020-05-11; So, iam converting it into epoc seconds, and I found the last date in sec (30th day).I was able to solve it like this, but I need with one "for" loop. Is it possible?
#!/usr/bin/perl
use Time::Local;
use Time::Localtime;
$day_a1 = 1;
$day_a2 = 30;
my ($year, $month, $day) = split('-', $epoc);
$epoc = timelocal($s, $m, $h, $day, $month-1, $year-1900);
$interval=$day_a2*60*60*24;
$epoc1=$epoc+$interval;
print scalar(localtime($epoc1)), "\n";
#x1=();
#date1=();
for ($d = $day_a1; $d <= $day_a2; $d++){
push (#x1, $d);
}
for ($d = $epoc; $d <= $epoc1; $d+=86400){
print scalar(localtime($d));
push (#date1, $d);
}
print #x1;
print #date1;

You would make your life a lot easier if you used the tools that are available for you. In particular, Time::Piece and Time::Seconds have been part of the standard Perl distribution since 2007.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use Time::Piece;
use Time::Seconds;
my $start = '2020-05-11';
# Add time of midday to avoid DST problems.
my $curr = Time::Piece->strptime("$start 12:00:00", '%Y-%m-%d %H:%M:%S');
for (1 .. 30) {
say $curr->strftime('%Y-%m-%d');
$curr += ONE_DAY;
}

Perhaps you intended to write code in following form
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use Time::Local;
use Time::Localtime;
my $days = 30;
my $date = '2020-05-11';
my $day_sec = 60*60*24;
my ($year, $month, $day) = split('-', $date);
my $epoch = timelocal(0, 0, 0, $day, $month-1, $year-1900);
for(my $day=0; $day<$days; $day++) {
say scalar localtime($epoch);
$epoch += $day_sec;
}

Related

Perl formatting array output.

I have a small program that I am trying to format the output.
The results get loaded in to an array - I am just having trouble formating the
printing out the array into a certain format.
#!/usr/bin/perl
use strict ;
use warnings ;
my #first_array ;
my #second_array ;
my #cartesian ;
while (<>) {
my $first_input = $_ ;
#first_array = split(' ', $first_input) ;
last ;
}
while (<>) {
my $second_input = $_ ;
#second_array = split(' ', $second_input) ;
last ;
}
while(my $first=shift(#first_array)) {
push(#cartesian, $first) ;
my $second = shift(#second_array) ;
push(#cartesian, $second ) ;
}
print "This is the merged array: #cartesian\n" ;
When I enter this in, I get this:
$ ./double_while2.pl
1 2 3
mon tue wed
This is the merged array 1 mon 2 tue 3 wed
what I want to print out is :
"1", "mon",
"2", "tue" ,
"3", "wed",
or alternately:
1 => "mon",
2 => "tue",
3 => "wed,
May I suggest a hash, since you are pairing things
my %cartesian;
#cartesian{ #first_array } = #second_array;
print "$_ => $cartesian{$_}\n" for sort keys %cartesian;
A hash slice is used above. See Slices in perldata
The arrays that you build had better pair up just right, or there will be errors.
If the goal is to build a data structure that pairs up elements, that can probably be done directly, without arrays. More information would help to comment on that.
Try to use hash instead.
for my $i(0..$#first_array){
$hash{$first_array[$i]} = $second_array[$i];
}
or else, you want format without using hashes, try as follows
for (my $i = 0; $i < $#cartesion/2; $i++) {
my $j = ($cartesion/2) + $i;
print "$cartesion[$i] $cartesion[$j] \n";
}
From your question and your code, I suppose that you are a lovely new 'victim' to perl ~
To merge two arrays with same lengh, I suggeest using 'map' to simplify your code:
my #cartesian = map {$first_array[$_], $second_array[$_]} 0..$#first_array;
and to format print style , you can define a subroutine to meet your different requirements:
sub format_my_array{
my $array_ref = shift;
my $sep = shift;
print $array_ref->[$_],$sep,$array_ref->[$_+1],"\n" for grep {! ($_%2)} 0..$#$array_ref;
}
Now, you can try calling your subroutine:
format_my_array(\#cartesian, " => ");
or
format_my_array(\#cartesian, " , ");
Now, you get what you want~
You may have noticed that some intermediate concepts are used in this answer, don't doute , that's exactly what I'm trying to introduce you to ~
May you the great happiness in learning perl ~
The trick is to go with Perl's strengths instead of fighting against them:
#!/usr/bin/perl
use strict;
use warnings;
# For say()
use 5.010;
my #first_array = split ' ', <>;
my #second_array = split ' ', <>;
if (#first_array != #second_array) {
die "Arrays must be the same length\n";
}
my #cartesian = map { $first_array[$_], $second_array[$_] } 0 .. $#first_array;
for (0 .. $#cartesian / 2) {
say "$cartesian[$_*2] => $cartesian[$_*2+1]";
}
But, it gets much easier still if you use a hash instead of an array for your merged data.
#!/usr/bin/perl
use strict;
use warnings;
# For say()
use 5.010;
my #first_array = split ' ', <>;
my #second_array = split ' ', <>;
if (#first_array != #second_array) {
die "Arrays must be the same length\n";
}
my %cartesian;
#cartesian{#first_array} = #second_array;
for (sort keys %cartesian) {
say "$_ => $cartesian{$_}";
}

Date ranges from start date and duration

New here, and to perl as well.
I'm trying to write a perl script to do some data archiving at work. Our directory structure is separated at one level by year, then has every day/month inside that.
So, for example:
\2016\1-1
\2016\1-2
....
\2016\12-20
etc...
I'm trying to make this script usable for every test (some of which are run in the future), so I have a prompt for the user to enter the year, which I use to populate the directory structure in the code. I'm having trouble with the month-date portion.
Usually tests are 3-4 days long. Right now I'm asking the user for the test length, then subtracting one. I do that because a 4 day test starting on the 8th, would be the 8 ,9 ,10, and 11th. If I don't subtract one, and just add the duration to the start date, it would go to the 12th.
I can't figure out how to take a start date and duration, and get that range of dates into an array, that I can then call when I need to find/create directories for the archiving.
Ideally, I'd like to have the user enter the year, start date, and length of the test, then create an array that will hold the month-day for all dates of the test. Then I could call those array dates when I create/find the needed directories.
Right now I'm still working on getting the inputs from the user, so that is all that I have.
Couple notes: I don't have every Perl module available to me. I have been using DateTime, and I'm not 100% sure what other modules I could use. Also, the directory structure does not have leading zeros on the month/date (i.e., 2-5, NOT 02-05).
Here is the input code that I have so far. It gives me a start date of today (which I need to change), and gives me the correct end date based on the test length. As you can see, I have not been able to get very far in this:
use warnings;
use DateTime;
my #test_dates;
print "What NAS?";
my $NAS = <STDIN>;
chomp($NAS);
print "What is the Multi-Mission mode (ops/nonops)?";
my $MM_Mode = <STDIN>;
chomp($MM_Mode);
print "What is the data mode (ops/sim/tst)?";
my $Data_Mode = <STDIN>;
chomp($Data_Mode);
print "Which spacecraft (NPP/J01)?";
my $sc = <STDIN>;
chomp($sc);
print "What LOM is being cleaned?";
my $LOM = <STDIN>;
chomp($LOM);
print "What MDMZ is being cleaned?";
my $MDMZ = <STDIN>;
chomp($MDMZ);
print "How many days is the test?";
my $Length = ( <STDIN> - 1 );
my $date = DateTime->from_epoch( epoch => time );
my $Duration = DateTime::Duration->new( days => $Length );
print "NAS is $NAS\n";
print "Multi-Mission mode is $MM_Mode\n";
print "Data Mode is $Data_Mode\n";
print "LOM is $LOM\n";
print "MDMZ is $MDMZ\n";
printf $date->ymd('/');
print #test_dates;
I don't think you have it clear in your mind exactly what you need, but this should help
use strict;
use warnings 'all';
use Time::Piece;
use Time::Seconds 'ONE_DAY';
print 'Enter start date (YYYY-MM-DD): ';
chomp(my $start = <>);
$start = Time::Piece->strptime($start, '%Y-%m-%d');
print 'Enter number of days: ';
chomp(my $duration = <>);
{
my $end = $start + ONE_DAY * ($duration-1);
die "Period crosses end of year" if $start->year != $end->year;
}
my $date = $start;
while ( --$duration ) {
my $dir = $date->strftime('\%Y\%m-%d');
$dir =~ s/\D\K0+//g;
print $dir, "\n";
$date += ONE_DAY;
}
output
E:\Perl\source>perl date_duration.pl
Enter start date (YYYY-MM-DD): 2016-7-18
Enter number of days: 22
\2016\7-18
\2016\7-19
\2016\7-20
\2016\7-21
\2016\7-22
\2016\7-23
\2016\7-24
\2016\7-25
\2016\7-26
\2016\7-27
\2016\7-28
\2016\7-29
\2016\7-30
\2016\7-31
\2016\8-1
\2016\8-2
\2016\8-3
\2016\8-4
\2016\8-5
\2016\8-6
\2016\8-7
Here is an approach that uses DateTime. You don't need to create a DateTime::Duration object, DateTime does that implicitly for you whenever you use date math.
use strict;
use warnings 'all';
use DateTime;
use feature 'say';
my $duration = 4;
my ($year, $month, $day) = (2016, 4, 9);
my $start = DateTime->new( year => $year, month => $month, day => $day);
my #dates;
foreach my $i ( 0 .. ( $duration - 1 )) {
my $date = $start->clone->add( days => $i )->strftime('\%Y\%m-%d');
$date =~ s/(?<=\D)0+//g;
push #dates, $date;
}
say for #dates;
I left out the user input stuff and just set some values. Basically it builds each day, creates the folder names and puts them into an array.
Output
\2016\4-9
\2016\4-10
\2016\4-11
\2016\4-12

Comparing elements in an array

I am working on writing a script that identifies login attempts that are 5 seconds or less apart, searching for brute force login attempts. So far I have been able to take the log timestamps and convert them to a readable and workable format, by using the script below:
#!/usr/bin/perl
use warnings;
use strict;
open my $IN, '<', 'test.txt' or die $!; # Open the file.
while (<$IN>) { # Process it line by line.
my $timestamp = (split)[1]; # Get the second column.
$timestamp =~ tr/://d; # Remove colons.
print "$timestamp\n";
}
The output I get looks like
102432
102434
104240
etc.
What I want to do is compare the numbers in the array to see if there is a five-second delay or less between login attempts. Something like:
if ($timestamp + 5 <= 2nd element in array) {
print "ahhh brute force"
}
The same thing all the way down the array elements until the end.
if (2nd element in array + 5 <= 3rd element in array) {
print "ahh brute force"
}
etc.
Could someone please point me in the right direction?
Example of input:
2014-08-10 13:20:30 GET Portal/jsjquery-latest.js 404 - "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
This will do as you ask. It uses Time::Piece, which has been a core module since version 10 of Perl 5, and so shouldn't need installing.
It uses both the date and the time fields from the log file to build Time::Piece objects, which can then be subtracted from one another to calculate the intervals.
The program expects the path to the log file as a parameter on the command line
use strict;
use warnings;
use 5.010;
use Time::Piece;
my $last_login;
while (<>) {
my #login = split;
my $login = Time::Piece->strptime("#login[0,1]", '%Y-%m-%d %H:%M:%S');
if ($last_login) {
my $interval = $login - $last_login;
if ($interval <= 5) {
printf "%s to %s is %d seconds\n", $last_login, $login, $interval;
}
}
$last_login = $login;
}
Update
As #knarf says in a comment, this can be done using a regular expression together with the Time::Local module's timelocal function.
This is a program that does something similar using that technique.
use strict;
use warnings;
use Time::Local 'timelocal';
my $last_login;
while (<>) {
next unless my #login = / (\d\d\d\d)-(\d\d)-(\d\d) \s+ (\d\d):(\d\d):(\d\d) /x;
$login[0] -= 1900;
$login[1] -= 1;
my $login = timelocal reverse #login;
if ($last_login) {
my $interval = $login - $last_login;
if ($interval <= 5) {
printf "%s to %s is %d seconds\n", map(scalar localtime $_, $last_login, $login), $interval;
}
}
$last_login = $login;
}

Perl array element manipulation

I've been trying and trying with this one, but it just doesn't seem to click.
If I have an array with let's say 6 numbers:
#a = (1,2,3,4,5,6)
How do I get every second index ( 2, 4, 6) in this case?
how do I compute the difference of every two elements, so
the output here would be:
1 1 1 (because 2-1 =1 and 4-3 =1 and so on..)
Note: don't ever use $a or $b, they're special (sort uses them) ... it's generally better to give your variables a descriptive name, name it as to what's in there rather than what type of variable it is.
for ( my $index = 0; $index < scalar( #pairs ); $index += 2 ) {
my $first = $pairs[ $index + 0 ];
my $second = $pairs[ $index + 1 ];
my $pair = $index / 2;
my $difference = $second - $first;
print "the difference of pair $pair is $difference\n";
}
I think you should post your earlier attempts. In my opinion, the best way to learn is to learn from your mistakes, not being presented a correct solution.
For this problem, I think I would use a C-style for-loop for the first part, simply because it is straightforward, and can easily be tweaked if some new requirement comes up.
The second problem can easily be solved using a regular Perl-style for-loop.
use strict;
use warnings; # always use these two pragmas
my #nums = 1..6;
my #idx;
for (my $n = 0; $n <= $#nums; $n += 2) { # loop from 0 to max index, step 2
push #idx, $n; # store number in #idx
}
print "Indexes: #idx\n";
my #diff;
for my $n (0 .. $#nums - 1) { # loop from 0 to max index minus 1
push #diff, $nums[$n + 1] - $nums[$n]; # store diff in #diff
}
print "Diff: #diff\n";
Output:
Indexes: 0 2 4
Diff: 1 1 1 1 1
Try this:
use strict;
use warnings;
my $index = 1;
my #a = (1,2,3,4,5,6);
for (#a) {
if ($index % 2 == 0) {
my $diff = $_ - $a[$index-2];
print $diff;
}
$index++;
}
You likely want to use the new List::Util pair functions.
For your first question:
use List::Util 'pairvalues';
my #seconds = pairvalues #list; # yields (2, 4, 6)
For your second question:
use List::Util 'pairmap';
my #diffs = pairmap { $b-$a } #list; # yields (1, 1, 1)
You can use map:
my #a = 1 .. 6;
print join ' ', 'Every second:', map $a[ 1 + $_ * 2 ], 0 .. $#a / 2;
print "\n";
print join ' ', 'Differences:', map $a[ 1 + $_ * 2 ] - $a[ $_ * 2 ], 0 .. $#a / 2;
print "\n";
First: Don't use variables a and b. $a and $b are special variables used in sorting. Just be a bit more descriptive of your variables (even if it's merely #my_array) and you should be fine.
You can loop through your array any which way you like. However, I prefer to use a while loop instead of the thee part for because the three part for loop is a bit misleading. It is a while loop in disguise and the promised indexing of the loop can be misleading.
#! /usr/bin/env perl
use warnings;
use strict;
use feature qw(say);
my #array = qw( 1 2 3 4 5 6 );
my $index = 1; # Remember Perl indexes start at zero!
while ( $index <= $#array ) {
say "Item is $array[$index]";
say "The difference is " . ($array[$index] - $array[$index-1]);
$index += 2;
}
You said every second element. Indexes of arrays start at 0, so you want the odd number elements. Most of the answers use map which is a very nice little command, but does an awful lot in a single line which can make it confusing for a beginner. Plus, I don't think the Perldoc on it is very clear. There should be more simple examples.
The say is a newer version of print. However say always adds a \n at the end. You should always use strict; and use warnings;. These will catch about 90% of your programming bugs.
The qw( ... ) is a quick way to make an array. Each word becomes an array element. You don't need quotes or commas.
#!/usr/bin/perl
use strict;
use warnings;
my #ar = (1, 2, 3, 4, 5, 6);
# 1. How do I get every second index ( 2, 4, 6) in this case?
my #even = map { $_ & 1 ? $ar[$_] : () } 0 .. $#ar;
# 2. how do I compute the difference of every two elements?
my (#c, #diff) = #ar;
push #diff, -1 * (shift(#c) - shift(#c)) while #c;
use Data::Dumper;
print Dumper \#even;
print Dumper \#diff;
1;
__END__
$VAR1 = [
2,
4,
6
];
$VAR1 = [
1,
1,
1
];

Perl - Split Array in to Smaller Evenly Distributed Arrays

How would I split a Perl array of arbitrary size in to a variable number of smaller arrays with the number of elements in each smaller array being distibuted as evenly possible? The original array must not be destroyed.
Off the top of my head:
use strict;
use warnings;
use Data::Dumper; # for debugging only
print Dumper(distribute(7, [1..30]));
# takes number+arrayref, returns ref to array of arrays
sub distribute {
my ($n, $array) = #_;
my #parts;
my $i = 0;
foreach my $elem (#$array) {
push #{ $parts[$i++ % $n] }, $elem;
};
return \#parts;
};
This guarantees that number of elements in #parts may only differ by one. There's anonther solution that would count the numbers beforehand and use splicing:
push #parts, [ #$array[$offset..$offset+$chunk] ];
$offset += chunk;
# alter $chunk if needed.
Here's a version using List::MoreUtils:
use strict;
use warnings;
use List::MoreUtils qw(part);
use Data::Dumper;
my #array = 1..9;
my $partitions = 3;
my $i = 0;
print Dumper part {$partitions * $i++ / #array} #array;
If you don't care what winds up in each array:
use strict;
use warnings;
use List::MoreUtils qw(part);
use Data::Dumper;
my $i = 0;
my $numParts = 2;
my #part = part { $i++ % $numParts } 1 .. 30;
print Dumper #part;
#Dallaylaen's answer doesn't quite work because you can't pass an array into a subroutine in Perl. Instead of passing in an array (or a list as Dallaylaen did in his example) you must pass in a reference to an array:
my #arrayIn = (1..30);
my #arrayOfArrays = distribute(7, \#arrayIn);
sub distribute {
my ($n, $array) = #_;
my #parts;
my $i = 0;
foreach my $elem (#$array) {
push #{ $parts[$i++ % $n] }, $elem;
};
return #parts;
};

Resources