Finding age in days from timestamp in Perl - perl

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

Related

Get previous hour of UTC/GMT time using Perl

I have a script which will print Start & End time of previous hour of UTC/GMT.
#!/usr/local/bin/perl
use strict;
use warnings;
use POSIX qw(strftime);
my ($tmp_date, $tmp_hour, $Start, $End);
my $date = strftime '%Y-%m-%d', gmtime();
print "Date:$date\n";
my $hour = strftime '%H', gmtime();
print "Hour:$hour\n";
if ($hour == "00"){
$tmp_date = $date-1;
$tmp_hour = "23";
} else {
$tmp_hour = $hour-1;
$tmp_date = $date;
}
$a = length($tmp_hour);
if ($a == 1 ){
$tmp_hour="0".$tmp_hour;
}
$Start = $tmp_date.".".$tmp_hour."00";
$End = $tmp_date.".".$hour."05";
if ($End =~ /0005/){
$tmp_date = `TZ=GMT-12 date +%Y%m%d`;
$End =$tmp_date.".".$hour."05";
}
print "Start:$Start, End:$End\n";
For example, lets say now UTC time is: Wed Jun 10 10:18:57 UTC 2020
This should print Start & End time as 2020-06-10.0900 2020-06-10.1005 respectively.
This script is working as expected. But when Daylight savings happens will there be any impact on fetching Start & End time?
I want experts suggestions how can I avoid unnecessary if statements and achieve it by the use of Perl module itself.
PS: Perl version: v5.10.1. Please suggest Perl modules which comes with standard Perl installation (Ex: POSIX, Time::Local etc.) for solution of above problem.
As you're using gmtime(), any DST changes will have no effect at all.
I'm not sure why your end time ends with '05', I would have thought that the end of the hour comes at '00'.
Here's how I'd write it with Time::Piece and Time::Seconds.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use Time::Piece;
use Time::Seconds;
my $end = gmtime->truncate(to => 'hour');
my $start = $end - ONE_HOUR;
my $format = '%Y-%m-%d %H:%M:%S';
say 'Start: ', $start->strftime($format);
say 'End: ', $end->strftime($format);
If you really want the end time to be five past the hour, then add this line after the ONE_HOUR line:
$end += (5 * ONE_MINUTE);
You can, of course, use any of the standard strftime() sequences to change the format of the output.

Comparing date and time and the same time in perl

I want to compare both date and time check if the timestamp from the file I'm going to open will have equal or greater date and time as if the my timestamp which looks like this:
$Date = "20170608";
$Time = "105006";
My main problem is how to do it efficiently possibly without adding perl libraries and how to check it when there's going to be situation of date switching and the hour will be for example 23:59:44
Time::Piece is core in perl, and supports 'strptime'.
#!/usr/bin/env perl
use strict;
use warnings;
use Time::Piece;
my $Date = "20170608";
my $Time = "10506";
my $ts = Time::Piece->strptime( "$Date $Time", "%Y%m%d %H%M%S" );
print $ts, "\n";
print "Delta:", $ts->epoch - time(), "\n";
Was unclear on what time that $Time represented - strptime converts it to 10:50:06, but I'm guessing it might be intended to be 01:05:06?
If so, then zero pad.
$Time = sprintf ( "%06d", $Time );
To read the timestamp from the file metadata, then you need stat:
my $mtime = (stat $filename)[9];

Add time to ISO 8601 times in Perl

I have an ISO 8601 time stored in a variable and I have some number of hours stored in another variable like this:
my $current_time = shift; #looks like: 2015-07-01T15:38:08Z
my $hours = shift; # looks like: 12
My goal is to add the hours to the current time, but there doesn't seem to be any built in Perl function to do it. In Powershell, you can do something like this:
$currentTime = $currentTime .AddHours($hours)
Is there an easy way to do this in Perl?
That specific ISO 8601 profile is also known as RFC3339.
use DateTime::Format::RFC3339;
my $dt = DateTime::Format::RFC3339->parse_datetime('2015-07-01T15:38:08Z');
$dt->add( hours => 1 );
print "$dt\n"; # 2015-07-01T16:38:08Z
If you want to accept arbitrary ISO 8601 profiles, you can use DateTime::Format::ISO8601.
use DateTime::Format::ISO8601;
my $dt = DateTime::Format::ISO8601->parse_datetime('2015-07-01T15:38:08Z');
$dt->set_time_zone('UTC'); # Convert to UTC ("Z") if it's not already.
$dt->add( hours => 1 );
print $dt->iso8601().'Z', "\n"; # 2015-07-01T16:38:08Z
I posted these alternatives because these modules are far less error-prone to use than Time::Piece.
You can also use Time::Moment. In the interest of full disclosure, I am the author of Time::Moment.
say Time::Moment->from_string('2015-07-01T15:38:08Z')
->plus_hours(1);
Output:
2015-07-01T16:38:08Z
Rather easy with Time::Piece:
#! /usr/bin/perl
use warnings;
use strict;
use Time::Piece;
use Time::Seconds;
my $current_time = '2015-07-01T15:38:08Z';
my $hours = 12;
my $format = '%Y-%m-%dT%H:%M:%SZ';
my $time = 'Time::Piece'->strptime($current_time, $format);
$time += $hours * ONE_HOUR;
print $time->strftime($format), "\n";

Perl - How to get date of Previous wednesday from the given date without using DateTime

All,
I want to find out the date of previous wednesday from the given date.
Eg. I have date as "20150804" and i would need "20150729".
DateTime is not available and i cannot install it as well.
I looked at few examples but they were using DateTime.
Can you please redirect me where i can get some help.? Thanks.
I am planning to code something like below.
Code:
#!/opt/perl-5.8.0/bin/perl
use warnings;
use strict;
my $dt="20150804";
my $prevWednesday=getPrevWednesday($dt);
sub getPrevWednesday()
{
my $givenDt=shift;
...
}
Another brute force approach, this time using another core module Time::Local.
#!/usr/bin/perl
use warnings;
use strict;
use Time::Local;
sub prev_wednesday {
my $date = shift;
my ($year, $month, $day) = $date =~ /(....)(..)(..)/;
my $time = timelocal(0, 0, 12, $day, $month - 1, $year);
do { $time -= 60 * 60 * 24 } until (localtime $time)[6] == 3; # <- Wednesday
my ($y, $m, $d) = (localtime $time)[5, 4, 3];
return sprintf "%4d%02d%02d\n", 1900 + $y, $m + 1, $d;
}
print $_, ' ', prev_wednesday($_), for qw( 20150804 20150805 20150806
20150101 20000301 20010301 );
Using Time::Piece :
use feature qw(say);
use strict;
use warnings;
use Time::Piece;
use Time::Seconds;
my $str = '20150804';
my $fmt = '%Y%m%d';
my $t = Time::Piece->strptime($str, $fmt);
do {
$t = $t - ONE_DAY;
} until ( $t->day eq 'Wed');
say $t->strftime($fmt);
There's always the brute force approach.
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use POSIX 'strftime';
my $ONE_DAY = 24 * 60 * 60;
# Get now
my $time = time;
# Subtract days until you get to a Wednesday
do {
$time -= $ONE_DAY;
} until (localtime($time))[6] == 3;
# Format
say strftime '%Y%m%d', localtime $time;
But if you're working in a Perl environment where you can't install modules from CPAN, then it is always worth working to get that restriction removed. Modern Perl programming is often a case of plumbing together the right series of CPAN modules. If you don't have access to CPAN then you're just making your life much harder than it needs to be.
If you really can't get the restriction lifted, then look for another job. It's not worth dealing with people who impose such pointless restrictions.
Update: Just noticed that you're also using a prehistoric version of Perl. You'll need to remove the use 5.010 and replace the say with print. And brush up your CV :-/
Update 2: choroba's solution is better. It deals with any date in the correct format. Mine just deals with the current date. The advice about fixing your working environment still holds though.
Here is a more elegant solution that does not do bruteforce.
use strict;
use warnings;
use Time::Local 'timelocal';
use POSIX 'strftime';
my $dt = "20150804";
say getPrevWednesday($dt);
# note you do not want () here,
# see http://perldoc.perl.org/perlsub.html#Prototypes
sub getPrevWednesday {
my $givenDt = shift;
# parse the string into a unix timestamp
my ( $year, $month, $day ) = $givenDt =~ /(....)(..)(..)/;
my $timestamp = timelocal( 0, 0, 12, $day, $month - 1, $year );
# get the day of week, ignore the rest
my ( undef, undef, undef, undef, undef, undef, $wday ) =
localtime $timestamp;
# because we start the week with Sunday on day 0
# and to get to the previous Wednesday from Sunday it's
# 4 days (Wednesday is 3) we can add 4 to the
# number of this day, divide by 7, take the leftover (modulo)
# and then subtract that many days
# (86_400 is one day in seconds)
# v- -6 ------
# 6 % 7 = 6
# +4 -----v
# v
# 0 1 2 3 4 5 6 0 1 2 3 4 5 6
# S M T W T F S S M T W T F S
my $prev_wed = $timestamp - ( ( $wday + 4 ) % 7 * 86_400 );
# go one week back if we got the same day
$prev_wed -= ( 7 * 86_400 ) if $prev_wed == $timestamp;
# debug output
warn "in: " . localtime($timestamp) . "\n";
warn "out: " . localtime($prev_wed) . "\n\n";
# put it back into your format
return strftime('%Y%m%d', localtime $timestamp);
}
Output:
# STDOUT
20150804
# STDERR
in: Tue Aug 4 12:00:00 2015
out: Wed Jul 29 12:00:00 2015

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.
}
}