Related
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";
# ...
}
}
Frankly I don't know Perl at all. I have to solve a problem using perl for some reasons. I tried searching for quick solutions and couldn't find any (my bad)
Problem: I have got a file that has list of file names and a timestamps (i.e. 2012-05-24T18:19:35.000Z) in it.
I need to parse identify which of these are more that 90 days old.
I just need the check, everything else I think I have got in place already. When I googled some people suggested using some fancy datetime packages while some suggestions were around using of -M.
Quite confused actually. All help appreciated. Thanks.
This format is defined by both RFC3339 (rather specifically) and ISO8601 (among many others).
use strict;
use warnings;
use feature qw( say );
use DateTime qw( );
use DateTime::Format::RFC3339 qw( );
my $limit_dt = DateTime->now->subtract( days => 90 );
my $format = DateTime::Format::RFC3339->new();
while (<>) {
chomp;
my ($timestamp, $filename) = split(' ', $_, 2);
my $dt = $format->parse_datetime($timestamp);
say($filename) if $dt < $limit_dt;
}
For example,
$ cat data
2012-05-24T18:19:35.000Z new
2012-02-25T18:19:35.000Z old
2012-02-24T18:19:35.000Z ancient
$ perl script.pl data
ancient
To ignore the time portion and just check if the date part is more than 90 days ago, use the following instead:
my $limit_dt = DateTime->today( time_zone => 'local' )->subtract( days => 90 );
That date format has the advantage that doing a lexicographical comparison between two of those string is (just about) the same as doing a date-time comparison. So you just need to get the date 90 days in the past into that format, and doing a string comparison.
use POSIX 'strftime';
$_90_days_ago = strftime("%FT%T.000Z", gmtime( time-90*86400 ));
...
foreach $date (#your_list_of_dates) {
if ($date lt $_90_days_ago) {
print "$date was at least 90 days ago.\n";
} else {
print "$date is less than 90 days ago.\n";
}
}
Something like this should work:
#! perl -w
use strict;
use Time::Local;
# 90 times 24 hours of 60 minutes, 60 seconds
my $ninety_days = 24 * 60 * 60 * 90;
my $now = time;
# parse the time stamps in the file
while (<INPUTFILE>)
{
chomp();
if (/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/)
{
my $year = $1;
my $month = $2;
my $day = $3;
my $hour = $4;
my $minute = $5;
my $second = $6;
# Looks like these are in GMT ("Z") so we'll use timegm
my $time = timegm($second,$minute,$hour,$day,$month - 1,$year - 1900);
if (($now - $time) > $ninety_days)
{
print "$_ is more than 90 days ago!
}
}
}
(that's just the basic - it needs the details about opening the data file, etc)
You should take a look to this Perl module https://metacpan.org/pod/Date::Manip
I thought this was going to be very simple but I am really out of options now. I want to substract 15 minutes from a given time.
Example
My time is 15:04 I want to substract 15 minutes to be 14:49. I have searched for solutions on the internet but there is no perl module that can help me out.
You can use DateTime:
my $dt = DateTime->new(
year => 1,
month => 1,
day => 1,
hour => 15,
minute => 4,
);
$dt->subtract(minutes => 15);
printf "%d:%d\n", $dt->hour, $dt->minute; # prints 14:49
Well it all depends on how your time is stored. I prefer to use a time_t as returned by the time built in.
my $now = time();
my $before1 = $now - (15*60); # 15 minutes ago
my $before2 = $now - (3*60*60); # 3 hours ago
my $before3 = $now - (2*24*60*60); # 2 days ago
For output I use the POSIX module
print POSIX::strftime( '%Y-%m-%d %T', localtime($before1) );
perl -MClass::Date -e 'my $d=Class::Date->new("2011-07-13 15:04:00"); my $d2 = $d-"15m"; print $d2, "\n";'
Output:
2011-07-13 14:49:00
Try using Date::Calc
use Date::Calc qw(Add_Delta_DHMS);
($year2, $month2, $day2, $h2, $m2, $s2) =
Add_Delta_DHMS( $year, $month, $day, $hour, $minute, $second, $days_offset, $hour_offset, $minute_offset, $second_offset );
($y,$m,$d,$H,$M,$S) = Add_Delta_DHMS(Today_and_Now(), 0, 0, -15, 0);
convert the time to unix time, for example the current time: $unixtime = time(); then subtract 15*60 from it then convert to a nice string with something like
sub display_time {
my ($sec,$min,$hour,$mday,$mon,$year,undef,undef,undef) = localtime(time);
$year += 1900;
$mon += 1;
return "$year.".sprintf("%02d.%02d %02d:%02d:%02d",$mon,$mday,$hour,$min,$sec);
}
You can use the below sub-routine if you are only concerned about time not date:
sub subTime{
my ($time) = #_;
my #splittime = split(':', $time);
my $hour = $splittime[0];
my $min = $splittime[1];
if($min < 15){
$min=($min+60)-15;
$hour-=1;
}
else{
$min = $min-15;
}
return "$hour:$min";
}
Disclamer: This was the solution OP used, he mentioned it in comments in above answer (in #eugene's answer).
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.
}
}
I have the following loop to calculate the dates of the current week and print them out. It works, but I am swimming in the amount of date/time possibilities in Perl and want to get your opinion on whether there is a better way. Here's the code I've written:
#!/usr/bin/env perl
use warnings;
use strict;
use DateTime;
# Calculate numeric value of today and the
# target day (Monday = 1, Sunday = 7); the
# target, in this case, is Monday, since that's
# when I want the week to start
my $today_dt = DateTime->now;
my $today = $today_dt->day_of_week;
my $target = 1;
# Create DateTime copies to act as the "bookends"
# for the date range
my ($start, $end) = ($today_dt->clone(), $today_dt->clone());
if ($today == $target)
{
# If today is the target, "start" is already set;
# we simply need to set the end date
$end->add( days => 6 );
}
else
{
# Otherwise, we calculate the Monday preceeding today
# and the Sunday following today
my $delta = ($target - $today + 7) % 7;
$start->add( days => $delta - 7 );
$end->add( days => $delta - 1 );
}
# I clone the DateTime object again because, for some reason,
# I'm wary of using $start directly...
my $cur_date = $start->clone();
while ($cur_date <= $end)
{
my $date_ymd = $cur_date->ymd;
print "$date_ymd\n";
$cur_date->add( days => 1 );
}
As mentioned, this works, but is it the quickest or most efficient? I'm guessing that quickness and efficiency may not necessarily go together, but your feedback is very appreciated.
A slightly improved version of friedo's answer ...
my $start_of_week =
DateTime->today()
->truncate( to => 'week' );
for ( 0..6 ) {
print $start_of_week->clone()->add( days => $_ );
}
However, this assumes that Monday is the first day of the week. For Sunday, start with ...
my $start_of_week =
DateTime->today()
->truncate( to => 'week' )
->subtract( days => 1 );
Either way, it's better to use the truncate method than re-implement it, as friedo did ;)
You can use the DateTime object to get the current day of the week as a number ( 1-7 ). Then just use that to find the current week's Monday. For example:
my $today = DateTime->now;
my $start = $today->clone;
# move $start to Monday
$start->subtract( days => ( $today->wday - 1 ) ); # Monday gives 1, so on monday we
# subtract zero.
my $end = $start->clone->add( days => 7 );
The above is untested but the idea should work.
Would this work:
use strict;
use warnings;
use POSIX qw<strftime>;
my ( $day, $pmon, $pyear, $wday ) = ( localtime )[3..6];
$day -= $wday - 1; # Get monday
for my $d ( map { $day + $_ } 0..6 ) {
print strftime( '%A, %B %d, %Y', ( 0 ) x 3, $d, $pmon, $pyear ), "\n";
}
I'm printing them only as an illustration. You could store them as timestamps, like this:
use POSIX qw<mktime>;
my #week = map { mktime(( 0 ) x 3, $day + $_, $pmon, $pyear ) } 0..6;
This should work:
use POSIX; # for strftime
my $time = time ();
my $seconds = 24*60*60;
my #time = gmtime ();
$time = $time - $time[6] * $seconds;
for my $wday (0..6) {
$time += $seconds;
my #wday = gmtime ($time);
print strftime ("%A %d %B %Y\n", #wday);
}
Gives me:
$ ./week.pl
Monday 24 May 2010
Tuesday 25 May 2010
Wednesday 26 May 2010
Thursday 27 May 2010
Friday 28 May 2010
Saturday 29 May 2010
Sunday 30 May 2010
If you want to get weeks starting on Sunday, change $time[6] to ($time[6] + 1).
This assumes you want the GMT weeks. Change gmtime to localtime to get local time zone weeks.