Perl: Loop through months in a specified range - perl

I need to make a loop (foreach) for all the months specified in a range like:
01-2013 to 09-2015 (month-year) format.
The tricky part is that in every loop i need the month - year data as well to run an sql query, so i cannot use a simple +1 counter.
I looked as Date::Calc and Date::Simple but it did not offer me a solution.
Does anybody have a code snippet i could use or come up with an idea on how to tackle this challenge?

The DateTime module has a nice function add which allows you to add whatever amount of time you want to an object:
use strict;
use warnings;
use DateTime;
use feature 'say';
my $start = DateTime->new(year => 2013, month => 1);
my $end = DateTime->new(year => 2015, month => 9);
while ($start <= $end) {
$start->add(months => 1);
say $start->strftime("%m-%Y");
}

If you only need to loop through the dates, why not just use this:
for my $year (2013..2015) {
for my $month (1..12) {
my $date = sprintf "%02d-%d", $month, $year;
# do your $date processing here
...
last if ($date eq "09-2015");
}
}

Date::Calc is awesome. Check it again
use Date::Calc();
my ($month, $year, $end_month, $end_year) = (1, 2013, 9, 2015);
while (($year < $end_year) || ($year == $end_year && $month <= $end_month)) {
print "year: $year, month: $month\n";
($year, $month) = Date::Calc::Add_Delta_YMD($year,$month,1,0,1,0);
}

my $start_date = '01-2013';
my $end_date = '09-2015';
my ($sm, $sy) = split '-', $start_date;
my ($em, $ey) = split '-', $end_date;
for my $y ($sy..$ey) {
for my $m (1..12) {
next if ($y==$sy && $m<$sm);
last if ($y==$ey && $m>$em);
# use $m and $y for further processing sql query
# print "Month: $m\t Year: $y\n";
# ...
}
}

Related

Calculating date time in perl

am new to perl am trying to do a script which can check
last seen / days / hours and minutes
i have made some try.
but i need help to make it function well
#!/usr/bin/perl
use DateTime;
my $Datetime = DateTime->now;
my $date = $Datetime->ymd;
my $time = $Datetime->hms;
$Seen_time = "11:50:02";
$Seen_day = "2022-01-12";
if ($Seen_time eq $time) {
print "His Online\n";
}
elsif ($Seen_time ne $time) {
# calculate how many minutes has passed from current time and seen time
print "He was online 3 minutes or hours back\n";
}
elsif ($Seen_day) {
# calculate days from date
print "he was online 2 days back\n";
}
else {
print "we are going to moon soon\n";
}
Here is a very rough implementation using DateTime::Format::Human::Duration
https://metacpan.org/pod/DateTime::Format::Human::Duration
as Håkon Hægland suggested, you will need to fine tune the output:
#!/usr/bin/perl
use DateTime;
use warnings;
use strict;
use diagnostics;
use DateTime::Duration;
use DateTime::Format::Human::Duration;
my $Datetime = DateTime->now;
my $Seen_time = "11:50:02";
my $Seen_day = "2022-01-12";
# You probably need to take the above and get it in a DateTime object this is just for the example:
my $seen = DateTime->new(year => 2022, day => 12, month => 1, hour => 11, minute => 50, second => 2);
#you could use the DateTime->compare method to compare two DateTime objects. The semantics are compatible with Perl's sort function;
#it returns -1 if $seen < $Datetime etc.
my $cmp = DateTime->compare( $seen, $Datetime );
if (!$cmp) {
print "He is Online\n";
}
elsif ($cmp == -1) {
my $d = DateTime::Duration->new();
my $span = DateTime::Format::Human::Duration->new();
my $dur = $Datetime - $seen;
# You can fine tune this output to just me hours or whatever:
print "He was online " . $span->format_duration($dur) . " ago \n";
}
else {
print "We are going to moon soon\n";
}

Convert Old Unix Date to Perl and compare

Requirement - I have file name called "Rajesh.1202242219". Numbers are nothing but a date "date '+%y''%m''%d''%H''%M'" format.
Now I am trying to write a perl script to extract the numbers from file name and compare with current system date and time and based on output of this comparison, print some value using perl.
Approach:
Extract the Digit from File name:
if ($file =~ /Rajesh.(\d+).*/) {
print $1;
}
Convert this time into readable time in perl
my $sec = 0; # Not Feeded
my $min = 19;
my $hour = 22;
my $day = 24;
my $mon = 02 - 1;
my $year = 2012 - 1900;
my $wday = 0; # Not Feeded
my $yday = 0; # Not Feeded
my $unixtime = mktime ($sec, $min, $hour, $day, $mon, $year, $wday, $yday);
print "$unixtime\n";
my $readable_time = localtime($unixtime);
print "$readable_time\n";
find Current time and compare...
my $CurrentTime = time();
my $Todaydate = localtime($startTime);
But the problem here is, I am not getting solution of how to extract 2 digit from $1 and assign to $sec, $min, etc. Any help?
Also, if you have good approach for this problem statement, Please share with me
I like to use time objects to simplify the logic. I use Time::Piece here because it is simple and light weight (and part of the core). DateTime can be another choice.
use Time::Piece;
my ( $datetime ) = $file =~ /(\d+)/;
my $t1 = Time::Piece->strptime( $datetime, '%y%m%d%H%M' );
my $t2 = localtime(); # equivalent to Time::Piece->new
# you can do date comparisons on the object
if ($t1 < $t2) {
# do something
print "[$t1] < [$t2]\n";
}
Might as well teach DateTime::Format::Strptime to make the comparison much simpler:
use DateTime qw();
use DateTime::Format::Strptime qw();
if (
DateTime::Format::Strptime
->new(pattern => '%y%m%d%H%M')
->parse_datetime('Rajesh.1202242219')
< DateTime->now
) {
say 'filename timestamp is earlier than now';
} else {
say 'filename timestamp is later than now';
};
my ($year, $month, $day, $hour, $min) = $file =~ /(\d{2})/g;
if ($min) {
$year += 100; # Assuming 2012 and not 1912
$month--;
# Do stuff
}
I think unpack might be a better fit.
if ( my ( $num ) = $file =~ /Rajesh.(\d+).*/ ) {
my ( $year, $mon, $day, $hour, $min ) = unpack( 'A2 A2 A2 A2 A2', $num );
my $ts = POSIX::mktime( 0, $min, $hour, $day, $mon - 1, $year + 100 );
...
}
Using a module that parses dates might be nice. This code will parse the date and return a DateTime object. Refer to the documentation to see the many ways to manipulate this object.
use DateTime::Format::Strptime;
my $date = "1202242219";
my $dt = get_obj($date);
sub get_obj {
my $date = shift;
my $strp = DateTime::Format::Strptime->new(
pattern => '%y%m%d%H%M'
);
return $strp->parse_datetime($date);
}

In Perl, how can I ensure that a string corresponds to a valid date?

I was wondering if there is a simple way in Perl to ensure that a date string corresponds to a valid date.
For example, 2012 02 30 is incorrect because it doesn't exist.
The DateTime module will validate dates when creating a new object.
$ perl -we 'use DateTime; my $dt;
eval { $dt = DateTime->new(
year => 2012,
month => 2,
day => 30);
}; print "Error: $#" if $#;'
Error: Invalid day of month (day = 30 - month = 2 - year = 2012) at -e line 1
It also works dynamically on a given DateTime object:
$dt->set(day => 30);
Something like this using Class::Date should work
perl testit.pl
Range check on date or time failed
use Class::Date;
my $d=Class::Date->new('2021-02-30');
unless ( $d->error ) {
print "good date\n";
} else {
print $d->errstr(). "\n";
}
exit;
Check here:
http://www.perlmonks.org/?node_id=564594
I believe you'll get the answers you seek from the wise monks.
You can do this through the use of POSIX mktime, but apparently only if you have a flexible-enough implementation of mktime.
What I do is plug the numbers in and then use local time to get them back and if I get the same day value back, it's a valid number. So, given your string:
my ( $y, $m, $d ) = split ' ', $date_string;
die "$date_string is not a valid date!"
unless ( $d == ( localtime mktime( 0, 0, 0, $d, $m - 1, $y - 1900 ))[3] )
;
See, in the versions of mktime that I'm used to, mktime( 0, 0, 0, 30, 1, 112 ) would make '2012-03-01' and 30 != 1
You can also use Time::Local:
#!/usr/bin/env perl
use strict; use warnings;
use Carp qw( croak );
use Time::Local qw( timegm );
my #to_check = ('1927 06 18', '2012 02 30');
for my $date ( #to_check ) {
printf "'%s' is %s\n", $date, check_date($date) ? 'valid' : 'invalid';
}
sub check_date {
my ($date) = #_;
my ($year, $month, $mday) = split ' ', $date;
my $ret;
eval {
$ret = timegm(0, 0, 0, $mday, $month - 1, $year - 1900);
};
return $ret && $ret;
}
May be this will help too:
use Time::Piece; #in perl CORE distro since 5.10
use 5.010;
say Time::Piece->strptime("2011-02-29","%Y-%m-%d")->strftime("%Y-%m-%d");
#2011-03-01
say Time::Piece->strptime("2012-02-29","%Y-%m-%d")->strftime("%Y-%m-%d");
#2012-02-29

Perl - How to convert a date?

How to convert date format YYYY-MM-DDTHH:MM:SSZ to YYYY-MM-DD HH:MM + 8 hours?
For example:
Input: 2011-07-07T18:05:45Z
Output: 2011-07-08 02:05
Let's start with Rahul's snippet, and add in the date math and output formatting...
use DateTime;
use DateTime::Format::ISO8601;
use DateTime::Format::Strptime;
my $string = '2011-07-07T18:05:45Z';
my $dt = DateTime::Format::ISO8601->parse_datetime( $string );
die "Impossible time" unless $dt;
my $formatter = new DateTime::Format::Strptime(pattern => '%Y-%m-%d %T');
$dt->add( hours => 8 )->set_formatter($formatter);
print "$dt\n";
I've added the use of DateTime::Format::Strptime, in order to specify the desired output format.
Then I've added three more lines:
First I create a formatter, and feed it the output pattern I desire.
Next I add eight hours to the original date, and I assign the output
formatter by chaining the set_formatter() call to the add() call.
Then I print it.
Are you using the DateTime modules?
Specifically, here's a link to DateTime::Format::ISO8601 that reads/writes ISO 8601 format you mentioned as your input.
If you don't have DateTime, you surely have Time::Piece:
use strict;
use warnings;
use Time::Piece;
use Time::Seconds qw(ONE_HOUR);
my $str = '2011-07-07T18:05:45Z';
my $t = Time::Piece->strptime($str, "%Y-%m-%dT%TZ");
$t += 8 * ONE_HOUR;
print $t->strftime("%Y-%m-%d %H:%M"),"\n";
Taken From
How can I validate a "yyyy-MM-dd'T'HH:mm:ssZ" date/timestamp in UTC with Perl?
use DateTime;
use DateTime::Format::ISO8601;
my $string = '2010-02-28T15:21:33Z';
my $dt = DateTime::Format::ISO8601->parse_datetime( $string ); die "Impossible time" unless $dt;
It doesn't work, result is 2010-02-28T15:21:33
Then, do it the hard way...
use Time::Local
use warnings;
use strict;
$time = '2010-02-28T15:21:33Z';
my ($year, month, day) = split (/-/, $time)
$year -= 1900; #Year is an offset of 1900
$month -= 1; #Months are 0 - 11
#Now split the time off of the day (DDTHH:MM:SS)
$day = substr($day, 0, 2);
time = substr($day, 3)
#Now split the time
(my $hour, $minute, $second) = split(/:/, $time);
$second =~ s/Z$//; #Remove Z
my $time_converted = timelocal($second, $minute, $hour, $day, $month, $year);
#Now you have the time, Add eight hours
my $hours_in_seconds = 8 * 60 * 60;
$time_converted += $hours_in_seconds;
# Almost done: Convert time back into the correct array:
($second, $minute, $hour, $day, $month, $year) = localtime($time_converted);
$year += 1900;
$month += 1;
# Now, reformat:
my $formatted_time = sprint (%04d-%02d-%02d %02d:%02d),
$year, $month, $day, $hour, $minute;

Using Perl, how do I compare dates in the form of YYYY-MM-DD?

I have an array with n strings in format of YYYY-MM-DD (Example, "2010-10-31").
How do I compare a date to the strings in this array?
For example, delete the strings more than 30 day ago?
The great thing about YYYY-MM-DD-formatted dates is that you can compare them using simple string comparison. In Perl, that's the lt and gt operators.
In this case, it sounds like you're just looking to check whether the dates in the array are earlier or later than a given target date (which just happens to be "30 days ago"). For that case, the approach I would take would be to first determine what the date was 30 days ago and then compare that as a string against each date in the array. I would not introduce the overhead of converting all the YYYY-MM-DD strings into "proper" date objects, epoch times, etc. and back just for the sake of testing which represents the earlier date.
#!/usr/bin/env perl
use strict;
use warnings;
my $thirty_days = 30 * 24 * 60 * 60;
my ($old_day, $old_month, $old_year) = (localtime(time - $thirty_days))[3..5];
my $cutoff = sprintf('%04d-%02d-%02d',
$old_year + 1900, $old_month + 1, $old_day);
my #dates = ('2010-10-12', '2010-09-12', '2010-08-12', '2010-09-13');
for my $date (#dates) {
print "$date\n" if $date gt $cutoff;
}
Guess there's more than one way to do it, but I like Date::Simple for stuff like this ..
An example from the docs:
use Date::Simple ('date', 'today');
# Difference in days between two dates:
$diff = date('2001-08-27') - date('1977-10-05');
# Offset $n days from now:
$date = today() + $n;
print "$date\n"; # uses ISO 8601 format (YYYY-MM-DD)
It's great for doing arithmetic on objects ++.
Only dates however, no hours, minutes or seconds
use strict; use warnings;
use DateTime ();
use DateTime::Duration ();
use DateTime::Format::Natural ();
my $parser = DateTime::Format::Natural->new;
my $now = DateTime->now;
my $delta = DateTime::Duration->new( days => 30 );
my $cutoff = $now->subtract_duration( $delta );
my #new_dates = map { $_->[1] }
grep { -1 == $_->[0] }
map {
chomp;
[
DateTime->compare(
$parser->parse_datetime( $_ ),
$cutoff
),
$_
]
} <DATA>;
print "#new_dates";
__DATA__
2010-07-31
2010-08-31
2010-09-30
2010-10-31
A good start is to read The Many Dates of Perl and the DateTime site.
The YYYY-MM-DD format is a form of ISO 8601 date representation. There are variants of it that are considered acceptable, such as YYYY-MM-DD and YYYYMMDD and even YYMM in older data. You should look at a definitive list before you choose a method to compare these dates.
If ISO 8601 dates strings are: 1) valid dates; 2) in the same format with or without the - delimiter; 3) lacking in leading and trailing whitespace, an attractive property is that you can sort or compare the strings with simple lexicographical string comparisons.
In general then:
IFF you aren't going to check if the dates are valid and IFF they are the same format, and IFF there is not leading or trailing whitespace, you can compare against another string representing the target date in that same format.
--- Otherwise ---
Decide on a CPAN module to parse your date string (or match it yourself),
Convert to epoch time if if your dates are in that range, (or use a CPAN module that does larger scale date / time manipulation like Date::Manip or Date::Calc)
Perform the arithmetic on the type of time (epoch time, absolute days, whatever)
Convert the time back into the format that you want...
Here is code that does that:
use warnings; use strict;
use Date::Calc qw/:all/;
my (#date_strings, #abs_days);
my $target=Date_to_Days(2010, 1, 15);
# set #date_string to "YYYY-MM-DAY" between some dates
for my $AbsDay(Date_to_Days(2009,1,1)..Date_to_Days(2011,12,31)) {
my ($year, $mon, $day)=Add_Delta_Days(1,1,1,$AbsDay-1);
my $s="$year-$mon-$day";
push #date_strings, $s;
}
foreach my $s (#date_strings) {
my ($year, $mon, $day);
if(($year, $mon, $day)=$s=~/^(\d+)-(\d+)-(\d+)/) {
my $days=Date_to_Days($year, $mon, $day);
push #abs_days, $days
if ($target-$days <= 30 && $target-$days >= -30 );
}
}
print "absolute day way:\n";
foreach my $days (#abs_days) {
my ($year, $mon, $day)=Add_Delta_Days(1,1,1,$days-1);
print "$year-$mon-$day\n";
}
You can use Time::ParseDate module,
use strict;
use warning;
use Time::ParseDate;
my #dates = ('2010-10-12', '2010-09-14', '2010-08-12', '2010-09-13');
my #dates =
grep {parsedate($_, NO_RELATIVE => 1, UK => 1) > parsedate('-30 days') }#dates;
#output: 2010-10-12 2010-09-14
I did it like this, kind of verbose but it's easy to understand and gets the job done. #out2 is a 2d array, I'm reading in values using a for loop. Each loop I compare the input with the #out2 to see if it's an earlier or later time/date. If it is then I write the values to the array and then compare the next input.
if ($year < $out2[$j][7]) {
$lt = 1;
goto JUMP;
}
if ($year > $out2[$j][7]) {
$gt = 1;
goto JUMP;
}
if ($month < $out2[$j][5]) {
$lt = 1;
goto JUMP;
}
if ($month > $out2[$j][5]) {
$gt = 1;
goto JUMP;
}
if ($day < $out2[$j][6]) {
$lt = 1;
goto JUMP;
}
if ($day > $out2[$j][6]) {
$gt = 1;
goto JUMP;
}
if ($time < $out2[$j][4]) {
$lt = 1;
goto JUMP;
}
if ($time > $out2[$j][4]) {
$gt = 1;
goto JUMP;
}
JUMP:
if ($lt == 1) {
$out2[$j][2] = "$dtime $month\/$day\/$year";
$out2[$j][4] = $time;
$out2[$j][5] = $month;
$out2[$j][6] = $day;
$out2[$j][7] = $year;
$lt = 0;
}
if ($gt == 1) {
$out2[$j][3] = "$dtime $month\/$day\/$year";
$out2[$j][4] = $time;
$out2[$j][5] = $month;
$out2[$j][6] = $day;
$out2[$j][7] = $year;
$gt = 0;
}
Why not the CORE since 5.10 Time::Piece and Time::Seconds, not the first few results of a CPAN search?
use strict;
use warnings;
use Time::Piece (); # we don't need to include overloaded locatime
use Time::Seconds;
use Data::Dumper;
my #dates = qw/2010-10-31 2012-10-16 2011-09-08/;
my $now = Time::Piece::localtime();
my #date_objects = map {
Time::Piece->strptime( $_, '%F') # %F is the same as %Y-%m-%d
} #dates;
my #filtered_dates = grep {
$now - $_ < (ONE_DAY * 30)
} #date_objects;
print Dumper(map($_->strftime('%F'), #filtered_dates));
To find a minimal date in a loop:
var minDate = ...;
var maxDate = ...;
foreach my $date ( #$dates ) {
if ($minDate gt $date){ # Less than.
$minDate = $date; # Minimal date.
}
if ($minDate lt $date){ # Greater than.
$minDate = $date; # Maxamal date.
}
}