create text files with mtime between time range - perl

I found the below one-liner, that creates 50 text files
perl -MPath::Tiny -wE'
path("dir/s".$_.".txt")->spew("some data...$_\n") for 1..50
but it creates all the files with mtime as current time.
Is it possible to have the mtime between a date range?. like Jan 2016 to Dec 2018 for the text files?.
I understand that I have to use Time::Piece module, but I don't see Time::Piece module in my RHEL Perl which is 5.010.

There is the touch method in Path::Tiny, and the rest is about splitting up the desired range of dates. Note that the script below creates files (10 by default) with very simple-minded names, so please run in a brand new directory so to not overwrite something
perl -MPath::Tiny -MTime::Piece -wE'
$n = shift // 10;
($beg, $end) = map {
Time::Piece->strptime($_, "%Y%m%d")->epoch
} (20160101, 20181201);
$step = int ($end-$beg)/$n;
for (1..$n) {
$f = path("s$_.txt");
$f->spew("data..$_\n");
$f->touch($beg += $s)
' 5
Chaining calls to the desired path(..)->spew(..)->touch(..) doesn't work since spew returns true/false and not an object. (The touch does return the object and so it can have methods chained to it, but here it has to go after spew since spew changes the modification time.)
The above creates files
-rw-rw-r--. 1 xxx 8 Jul 31 2016 s1.txt
-rw-rw-r--. 1 xxx 8 Mar 1 2017 s2.txt
-rw-rw-r--. 1 xxx 8 Sep 30 2017 s3.txt
-rw-rw-r--. 1 xxx 8 May 1 2018 s4.txt
-rw-rw-r--. 1 xxx 8 Nov 30 2018 s5.txt
You'll probably want to fiddle with the step size and first/last time stamps.
Apparently there is a problem with Time::Piece, likely indicating a problem with that system† since the module has been in core since 5.9.5. Then I'd use Time::Local::timelocal
use Time::Local;
my $seconds_epoch = timelocal $sec, $min, $hour, $mday, $mon, $year;
where $mday goes from 1 while all other start at 0. Note that while it is traditional to provide the year subject to +1900 (so 116 for 2016) the normal years are accepted as well. So
$beg = timelocal 0, 0, 0, 1, 0, 2016;
$end = timelocal 0, 0, 0, 31, 11, 2018;
or, for more flexibility with dates
($beg, $end) = map {
($yr, $m, $d) = unpack "A4A2A2";
timelocal 0, 0, 0, $d, $m-1, $yr
} (20160101, 20181231)
†  The problem of the missing Time::Piece is described in this post in Dave Cross's blog. Also see comments under the question and a chat.

Related

Perl convert time in localtime format to YYYYMMDD

I have a string containing a date with this format
my $time = 'Fri Jan 8 14:24:27 2016';
And i want to convert it to YYYYMMDD.
I tried a couple of options such as :
use POSIX qw(strftime);
print strftime("%Y%m%d", $time);
But it won't work.
Tried localtime($time) as well as many others but it won't work.
I guess i need to convert it to an intermediary format ?
Note : i need to use strftime/POSIX as i can't call other modules for many reasons. (no Date::Time etc.)
thanks in advance,
Use Time::Piece, part of the Perl core since 2007:
use Time::Piece;
my $time = 'Fri Jan 8 14:24:27 2016';
say Time::Piece->strptime($time, '%a %b %d %H:%M:%S %Y')->ymd("");
You're trying to use POSIX::strftime() incorrectly. It's always a good idea to check the documentation, which says this:
strftime
Convert date and time information to string. Returns the string.
Synopsis:
strftime(fmt, sec, min, hour, mday, mon, year, wday = -1, yday = -1, isdst = -1)
The month (mon), weekday (wday), and yearday (yday) begin at zero, i.e., January is 0, not 1; Sunday is 0, not 1; January 1st is 0, not 1. The year (year ) is given in years since 1900, i.e., the year 1995 is 95; the year 2001 is 101. Consult your system's strftime() manpage for details about these and the other arguments.
So passing it a string containing a random(ish) representation of a datetime was slightly optimistic.
You need to parse your string and extract the bits that you need to pass to strftime(). As you've already be shown, the Modern Perl way to do that is to use a module like Time::Piece. But it's perfectly possible to do it with standard Perl too.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use POSIX 'strftime';
my $time = 'Fri Jan 8 14:24:27 2016';
my ($day, $mon, $date, $hr, $min, $sec, $year) = split /[\s:]+/, $time;
my %months = (
Jan => 0, Feb => 1, Mar => 2, Apr => 3,
May => 4, Jun => 5, Jul => 6, Aug => 7,
Sep => 8, Oct => 9, Nov => 10, Dec => 11,
);
$time = strftime('%Y%m%d',
$sec, $min, $hr, $date, $months{$mon}, $year - 1900);
say $time;
But don't do that. Use a module.

Anonymous subroutine call in Perl

Trying to understand a piece of code in Perl as follows
my #a = qw(A B C D E F F A K O N D);
my #b = qw(S F S T F F S);
$s=sprintf "%s %s %d %d:%d:%d %d: %s" , sub { ($a[$_[6]], $b[$_[4]], $_[3], $_[2], $_[1], $_[0], $_[5]+1900) }->(localtime), $s;
This code is in a method in a package which has been called with three arguments, arrays a and b have been defined in the method above in this code and s is a defined string.
All I can understand is that it is an anonymous subroutine which is expected to return multiple values due to localtime function but I just cannot understand the mechanism that this code follows. I assume I have given enough information to ask this question if not let me know.
The syntax is an unusual way of encapsulating the modification of several values at once inside of one statement.
This code is probably used at the end of a subroutine, as it returns a list of values. The real code might look like this.
sub foo {
my #a = qw(Sun Mon Tue Wed Thu Fri Sat);
my #b = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
my $s = q{foobar};
# wday month mday hour min sec year
return sub { ( $a[ $_[6] ], $b[ $_[4] ], $_[3], $_[2], $_[1], $_[0], $_[5] + 1900 ) }
->(localtime), $s;
}
Let's go through that. I added the return to make it more clear what's happening.
There is an anonymous subroutine sub { ... }, which is defined and directly being called sub { ... }->(). This is like a lambda function. The arrow operator -> in this form is used for calling a code reference. It differs from the direct object syntax (e.g. $foo->frobnicate()) in that there is no method name after the arrow.
my $code = sub { print "#_" };
$code->(1, 2, 3);
This will output:
1, 2, 3
The localtime return value in list context is passed. That's a bunch of values for the current time, as mentioned in the documentation.
# 0 1 2 3 4 5 6 7 8
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
localtime(time);
Without arguments, localtime implicitly uses time as its argument. Inside of the anonymous subroutine, those values end up in #_, which is the argument list of the sub. So $_[0] is the seconds part of the current local date.
Then there is #a and #b, which are really badly chosen names. If it's supposed to be short, I'd at least have called them #d and #m for weekdays and months.
The anonymous sub returns a list of values. It's the current weekday, as looked up in the #a array, where Sunday is index 0, the current month as looked in #b, the day, hour, minute and second, and the year as a four-digit number (hence the + 1900).
The overall function foo returns this list, and another value $s as the last element of its return value list.
If you were to print all of that, you would get:
print join q{ }, foo();
__END__
Mon Sep 25 16 10 33 2017 foobar
In the context of the sprintf, this becomes more clear. It's likely for adding a timestamp to a log entry.
sub log {
my $s = shift;
my #a = qw(Sun Mon Tue Wed Thu Fri Sat);
my #b = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
# 0 1 2 3 4 5 6 7
$s = sprintf "%s %s %d %d:%d:%d %d: %s",
# wday month mday hour min sec year
# 0 1 2 3 4 5 6
sub { ( $a[ $_[6] ], $b[ $_[4] ], $_[3], $_[2], $_[1], $_[0], $_[5] + 1900 ) }
# 7
->(localtime), $s;
return $s;
}
print &log('stuff happened'); # we need the & because CORE::log
I've marked each value with the position of the argument inside the sprintf pattern. $s seems to be the log message, which is replaced by the new string with the timestamp, and itself as the last argument.
However, this is a very unusual implementation, given that localtime will return the exact same thing in scalar context.
print &log("stuff happened\n");
print ''.localtime . ": stuff happened\n";
__END__
Mon Sep 25 16:24:40 2017: stuff happened
Mon Sep 25 16:24:40 2017: stuff happened
It's the return value of ctime(3). I first thought maybe it's a reimplementation because on the target system that kind of timestamp does not exist, but the localtime docs also say that this is not system-dependent.
The format of this scalar value is not locale-dependent but built into
Perl. For GMT instead of local time use the gmtime builtin. See also
the Time::Local module (for converting seconds, minutes, hours, and
such back to the integer value returned by time), and the POSIX
module's strftime and mktime functions.
The implementation with the sub is elegant, though not very Perl-ish. However, it's completely useless because Perl already brings this exact same format.
You could easily replace my log function with this.
sub log {
my $s = shift;
$s = localtime . " $s";
return $s;
}
But then again, if #a and #b are maybe localized, this makes makes sense. You give them as letter, which is probably not your real code, but I could see this being used as for example translating it to German like this.
use utf8;
sub log {
my $s = shift;
my #a = qw(So Mo Di Mi Do Fr Sa);
my #b = qw(Jan Feb Mär Apr Mai Jun Jul Aug Sep Okt Nov Dez);
# 0 1 2 3 4 5 6 7
$s = sprintf "%s %s %d %d:%d:%d %d: %s",
# wday month mday hour min sec year
# 0 1 2 3 4 5 6
sub { ( $a[ $_[6] ], $b[ $_[4] ], $_[3], $_[2], $_[1], $_[0], $_[5] + 1900 ) }
# 7
->(localtime), $s;
return $s;
}
print &log("stuff happened\n");
Now it makes a lot more sense.

perl print current year in 4 digit format

how do i get the current year in 4 digit this is what i have tried
#!/usr/local/bin/perl
#months = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
#days = qw(Sun Mon Tue Wed Thu Fri Sat Sun);
$year = $year+1900;
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
print "DBR_ $year\\$months[$mon]\\Failures_input\\Failures$mday$months[$mon].csv \n";
This prints DBR_ 114\Apr\Failures_input\Failures27Apr.csv
How do I get 2014?
I am using version 5.8.8 build 820.
use Time::Piece;
my $t = Time::Piece->new();
print $t->year;
Move the line:
$year = $year+1900;
To after that call to localtime() and to become:
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
$year = $year+1900;
The best way is to use the core library Time::Piece. It overrides localtime so that the result in scalar context is a Time::Piece object, you can use the many methods that the module supplies on it. (localtime in list context, as you have used it in your own code, continues to provide the same nine-element list.)
The strftime method allows you to format a date/time as you wish.
This very brief program produces the file path that I think you want (I doubt if there should be a space after DBR_?) Note that there is no need to double up backslashes inside a single-quoted string unless it is the last character of the string.
use strict
use warnings;
use Time::Piece;
my $path = localtime->strftime('DBR_%Y\%b\Failures_input\Failures%m%d.csv');
print $path;
output
DBR_2014\Apr\Failures_input\Failures27Apr.csv
One option to get the 4 digit year:
#!/usr/bin/perl
use POSIX qw(strftime);
$year = strftime "%Y", localtime;
printf("year %02d", $year);
You can also use
my ($y,$m,$d) = Date::Calc::Today();
$y variable will contain 2019
$m variable will contain 8
$d variable will contain 9
at the time of writing this answer ( 9th August 2019 )
The simplest way, I find, to get the year is:
my $this_year = (localtime)[5] + 1900;

Hash Logic within the script

I am trying to understand the logic of the following script specially in terms of storing content within the hash and time scan, also any suggestion on the improvement to make it more short.
#!perl
use strict;
use warnings;
my $A = 60; # minutes
my #mth = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
my #f = localtime();
my $TODAY = sprintf "%02d/%s/%4d",$f[3],$mth[$f[4]],$f[5]+1900;
my $START_MINUTE = $f[2]*60+$f[1] - $MAX_AGE;
##
my %users;
my %conn;
while (<DATA>) {
if( /\bAT\b/ ) {
my( $conn, $uid ) = /conn=(\d+).*uid=(.*?),/;
$conn{$conn} = $uid;
}
if( /ABB/ ) {
my ($timestamp, $conn) = /\[(.*?)\] conn=(\d+)/;
my ($date,$h,$m,undef) = split ':',$timestamp,4;
next unless ($date eq $TODAY);
my $minutes = $h*60 + $m;
if ($minutes >= $START_MINUTE){
my $uid = $conn{$conn};
++$users{$uid};
}
}
}
for my $uid (keys %users) {
my $count = $users{$uid};
print "$count\n" if $count > 6;
}
_DATA_
[04/Jun/2013:13:06:13 -0600] conn=13570 op=14 msgId=13 - AT dn="conn=ad1222,o=xyz.com" method=128 version=3
[04/Jun/2013:15:06:13 -0600] conn=13570 op=14 msgId=15 - RESULT ABB
There are two places where data is put in a hash
my( $conn, $uid ) = /conn=(\d+).*uid=(.*?),/;
$conn{$conn} = $uid;
}
This is straight forward, the regexp extracts the $uid and $conn and sets a hash entry with $conn as the key and $uid as the value. In this statement
$conn{$conn}
^^^^^^ ^ this is a hash
^^^^^ this is a completely different scalar
Overall the expression $conn{$conn} refers to a single element of the hash %conn with the scalar key $conn. There are two different variables here with basically the same name!
If you are looking for improvements, stylistically the hash should be called %uid as it's values are uids
if ($minutes >= $START_MINUTE){
my $uid = $conn{$conn};
++$users{$uid};
This is a bit more "that crazy perl" stuff, although really it is straight forward and is widely used in code. All it it does is increment the hash entry for the key $uid. If there is no entry for $user{$uid} already then the statement automatically makes it and sets the value to 1
update to discuss the "time scan"
my $A = 60; # minutes
my #mth = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
my #f = localtime();
my $TODAY = sprintf "%02d/%s/%4d",$f[3],$mth[$f[4]],$f[5]+1900;
my $START_MINUTE = $f[2]*60+$f[1] - $MAX_AGE;
This makes "$TODAY" which is the date today in a format that matches dates in the files and $START_MINUTE which is the number of minutes since midnight at the time the script is run
Later in the script the time of day is extracted and the minutes since midnight are found in a similar way (hour * 60 + minutes)
To "improve" this part of the script strftime could be used instead of the #mth array and the sprintf line
The calculations for the minutes could be moved to a sub called something like sub minutes_since_midnight
Bit difficult to say about improving the use of the hashes as it's not clear what they are used for out of the context of the program segment shown
Hope that more or less answers your question!!
any suggestion on the improvement to make it more short
You don't want to make it shorter to improve it. You need make it easier to understand in order to improve it. You already had problems understanding the logic, making it shorter isn't going to help there.
Let's take this:
my $A = 60; # minutes
my #mth = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
my #f = localtime();
my $TODAY = sprintf "%02d/%s/%4d",$f[3],$mth[$f[4]],$f[5]+1900;
my $START_MINUTE = $f[2]*60+$f[1] - $MAX_AGE;
I could go through the logic and attempt to figure it out, but more likely, I'll be making the same logical assumptions of the original author. Instead, we can improve readability by using good variable names, and by expanding the logic:
my #month_list = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
my ( $sec, $min, $hour, $month, $mday, $year ) = localtime; #Whoops!
my $full_year += 1900;
my $text_month = $month_list[$month];
my $today = sprintf "%02d/%s/%4d", $mday, $text_month, $full_year;
This is longer to type, but efficiency wise, it's just as efficient. Just because you can cram a bunch of operations on a single line doesn't make it faster to execute. However, mine is much easier to read and easier to maintain which will save you many hours of work. For example, my parsing of localtime is taken directly from the Perldoc on localtime. If you find an issue, and you think it could be due to my parsing of localtime, you could quickly compare my code to the Perldoc.
In fact, there is an error. Take a look of the localtime documentation and compare it to what I have, and you'll see I have $month and $mday mixed up.
Even better would be to use Time::Piece. In fact, Time::Piece would have made parsing the timestamp much cleaner too.
So, please understand that shorter code isn't better if it's just harder to understand, and it is usually not any more efficient in execution.

Calculating a delta of years from a date

I am trying to figure out a way to calculate the year of birth for records when given the age to two decimals at a given date - in Perl.
To illustrate this example consider these two records:
date, age at date
25 Nov 2005, 74.23
21 Jan 2007, 75.38
What I want to do is get the year of birth based on those records - it should be, in theory, consistent. The problem is that when I try to derive it by calculating the difference between the year in the date field minus the age, I run into rounding errors making the results look wrong while they are in fact correct.
I have tried using some "clever" combination of int() or sprintf() to round things up but to not avail. I have looked at Date::Calc but cant see something I can use.
p.s. As many dates are pre-1970, I cannot not unfortunately use UNIX epoch for this.
Have you tried DateTime? It'll handle parsing as well as subtraction.
Perl's gmtime and localtime functions have no problem handling negative input and dates before 1970.
use Time::Local;
$time = timegm(0,0,0,25,11-1,2005-1900); # 25 Nov 2005
$birthtime = $time - (365.25 * 86400) * 74.23; # ~74.23 years
print scalar gmtime($birthtime); # ==> Wed Sep 2 11:49:12 1931
The actual birthdate could be different by a few days, since one one-hundredth of a year only gives you a resolution of 3-4 days.
Use DateTime and DateTime::Duration.
When you substract a DateTime::Duration from a DateTime you get an other DateTime.
use strict;
use warnings;
use DateTime::Format::Strptime;
use DateTime::Duration;
my $fmt = DateTime::Format::Strptime->new(
pattern => '%d %b %Y',
locale => 'en_US',
);
my $start = $fmt->parse_datetime($ARGV[0]);
my $age = DateTime::Duration->new(years => $ARGV[1]);
my $birth = $start - $age;
print $fmt->format_datetime($birth), "\n";
Here is an example on how to invoke it:
$ perl birth.pl "25 Nov 2005" 74.23
25 Sep 1931
$ perl birth.pl "21 Jan 2007" 75.38
21 Sep 1931
I'd second Oesor's recommendation (second time today), and reiterate mobrule's reminder that perl handles negative dates. So DateTime is preferable.
But I would like to illustrate that this can be done with POSIX::mktime:
my ( $year1, $mon1, $day1 ) = qw<1944 7 1>;
my ( $year2, $mon2, $day2 ) = qw<2006 5 4>;
my $time1 = POSIX::mktime( (0) x 3, $day1, $mon1 - 1, 72 );
my $time2 = POSIX::mktime( (0) x 3, $day2, $mon2 - 1, 72 );
my $years = $year2 - $year1 - ( $time2 < $time1 ? 1 : 0 );
# 61 years
The caveat is that perl's internal clock handles dates back to December 14th, 1902 (actually 13th, after noon and before 6 PM), before which mktime starts returning undef. So for 99% of the people alive today, this will probably do.
Pointless trivia: scalar localtime( 0x80000000 ) : 'Fri Dec 13 15:45:52 1901' <- that's the cutoff ( 0x80000000 being 2s-complement minimum integer )