understanding issue with year got from localtime in perl - perl

I have seen many blogs saying the localtime function returns years from 1900.
But for me when I add 180 days to the current date and print it using localtime it's showing the proper year as 2016 instead of 116.
Example:
$today_date=timelocal(0,0,0,27,03,2016);
$seconds=180*60*24*60;
$date=today_date+seconds;
$new_date=localtime(date);
print strftime("%d-%m-%Y",$new_date);
Output:
24-10-2016
When I give input as 116 instead of 2016 as year to timelocal It still gives the output as 2016.How is this possible?

Your sourcecode is wrong, you're missing the $ signs in front of the variable names.
You start by calling timelocal from the module Time::Local which takes the number of years since 1900 as input, but pass 2016 as year value. In the end, you pass this date back to localtime which is exactly the opposite function.
Try this:
print time;
print join(" ",localtime(time));
1461783061
1 51 20 27 3 116 3 117 1
You get:
Second 1
Minute 51
Hour 20
Day 27
Zero-based month 3 which is April
1900-based year 116 which is 2016
My default usage of localtime is:
my #now = localtime(time);
# Convert zero-based-month to calendar month
++$now[4];
# Convert 1900-based-year to calendar year
$now[5] += 1900;
If all you need is a date calculation, try Date::Calc
use Date::Calc qw(Today Add_Delta_Days);
print join '-', Today();
print join '-', Add_Delta_Days(Today(),1);
2016-4-27
2016-4-28
or DateTime:
use DateTime;
print DateTime->now->add(days => 1, seconds => 180);
2016-04-28T19:00:12
DateTime's strftime method or formatter option will give you any output you want.

Related

How can I convert the epoch time to YMD in Perl?

I am trying to convert a date from epoch to year month day and get the correct date.
my $day = 18322;
my ($y, $m, $d) = (gmtime 86400*$day)[5,4,3];
The epoch date is 1583020800 The conversion is as follows $y is 120 $m is 2 $d is 1
I guess I have to add $y = $y+1900 I get the correct year, I can add 1 to $m to get the correct month the day $d I don't have to add anything to. Is this correct. I am taking over code for someone but I have no idea what [5,4,3] does.
Epoch time 1583020800 is Sun Mar 1 00:00:00 2020.
You can use gmtime, but it's awkward. It returns an array of values and they need to be converted. The year is the number of years since 1900 and the month starts at 0. This is because it is a thin wrapper around struct tm from the C programming language Perl is written in.
my($y,$m,$d) = (gmtime(1583020800))[5,4,3];
$y += 1900;
$m += 1;
printf "%04d-%02d-%02d\n", $y, $m, $d;
Instead, use the built in Time::Piece.
use v5.10;
use Time::Piece;
my $time = Time::Piece->gmtime(1583020800);
say $time->ymd;
Or the more powerful DateTime.
use v5.10;
use DateTime;
my $dt = DateTime->from_epoch(epoch => 1583020800);
say $dt->ymd;
The (...)[5,4,3] is a literal list slice. The thing inside the parens creates a list, but this selects only elements 5, 4, and 3.
The gmtime docs point to localtime, which shows you the position of each thing in its list:
localtime
Converts a time as returned by the time function to a 9-element
list with the time analyzed for the local time zone. Typically
used as follows:
# 0 1 2 3 4 5 6 7 8
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
localtime(time);
I would use Time::Piece as in Schwern's answer. But just to cover all bases, you can use the strftime() function from POSIX.pm as well.
use feature 'say';
use POSIX qw[strftime];
say strftime('%Y-%m-%d', gmtime(1583020800));
Output:
2020-03-01
You can pass different format strings to strftime().

Perl convert localtime to unix (epoch) time

using perl, I am trying to estimate the time since a file was created.
I would like to convert the local time to unix time (epoch), then take unix time of the file & subtract.
The problem I face is that when I convert localtime to unixtime , it is converted incorrectly!
my $current = str2time (localtime(time));
print $current;
The results I get are
2768504400 = Sun, 23 Sep 2057 21:00:00 GMT
2421349200 = Sun, 23 Sep 2046 21:00:00 GMT
Do I have to feed str2time with a specific date format?
You're doing something bizarre here - localtime(time) takes - the epoch time (time) and converts it to a string.
And then you convert it back.
Just use time()
Or perhaps better yet -M which tells you how long ago a file was modified. (In days, so you'll have to multiply up).
e.g.:
my $filename = "sample.csv";
my $modification = -M $filename;
print $modification * 84600;
But if you really want to take the time and convert it back again - you'll need to look at how localtime(time) returns the result.
If you do:
print localtime(time);
You get:
5671624811542661
Because localtime is being evaluated in a list context, and so returning an array of values. (Which you can use without needing to parse).
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
localtime(time);
If you do it in a scalar context, it returns a string denoting the time:
print "".localtime(time);
Gives:
Thu Sep 24 16:09:33 2015
But note - that might vary somewhat depending on your current locale. That's probably why str2time is doing odd things - because it makes certain assumptions about formats that don't always apply. The big gotcha is this:
When both the month and the date are specified in the date as numbers they are always parsed assuming that the month number comes before the date. This is the usual format used in American dates.
You would probably be better off instead using Time::Piece and strftime to get a fixed format:
e.g.
use Time::Piece;
print localtime(time) -> strftime ( "%Y-%m-%d %H:%M:%S" );
Note - Time::Piece overloads localtime so you can actually use it (fairly) transparently. Of course, then you can also do:
print localtime(time) -> epoch;
And do without all the fuss of converting back and forth.
You have missed requesting localtime to produce scalar (string) instead of array.
use Date::Parse;
my $current = str2time (scalar(localtime(time)));
print $current, "\n";
print scalar(localtime($current)),"\n";
perldoc -f localtime
Converts a time as returned by the time function to a 9-element
list with the time analyzed for the local time zone. Typically
used as follows:
# 0 1 2 3 4 5 6 7 8
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
...
In scalar context, "localtime()" returns the ctime(3) value:
$now_string = localtime; # e.g., "Thu Oct 13 04:54:34 1994"

Perl: Setting $day, $month and $year

I've managed to cobble together a script that reads through thousands of log entries and creates a summary of them. All good so far. What I also want to be able to do is for it to create a separate summary of the entries from just the last 90 days.
A single entry in the log looks like the following, with newer entries always being added to the bottom of the file :
Serial No: 10123407
Date: 14/08/15
Time: 12:58
Cycle type: 134 U
Hold time: 0180
Cycle No: 1357
Dry Time: 00 mins.
Cycle Start
12:58.35
Hold Time 0000 Secs
Cycle: Failed
User_Message 13
Ref.to User Manual
Cycle End
13:01.32
The code I am using to return the current date and the date 90 days ago is:
use POSIX qw(strftime);
use Time::Local qw(timegm);
my ($d,$m,$y) = (localtime())[3,4,5];
print OUT (strftime("%d/%m/%y - ", gmtime(timegm(0,0,0,$d,$m,$y)-90*24*60*60)));
print OUT (strftime("%d/%m/%y\n", gmtime(timegm(0,0,0,$d,$m,$y))));
I'm doing it like this because it's producing my dates in the format I want, the same as in the logs dd/mm/yy and always zero padded.
Using this I get the following output:
11/05/15 - 09/08/15
So if I can print it, how can I store the data as the variables: $day90, $month90, $year90, $day, $month and $year. If I can do that then I think I can do the logical operations necessary to decide if the log entry is within the last 90 days and then create my summary as I want it.
I don't have any preconceived ideas as to how this is done so any and all solutions will be very much appreciated.
One of the best ways to compare dates is by converting them to %Y%m%d format (or %Y-%m-%d if you want something more readable) and then you can compare them as text strings
You can use the core module Time::Piece to do the formatting for you
Here's an example. It defines the input format and the comparison format as $dmy_format and $ymd_format respectively
The strings for today's date and 90 days earlier are defined and stored as state variables the first time in_range is called, and so never need to be calculated again. (You will need Perl 5 version 10 or better for the state keyword. If that's not available then just use my instead and move those definitions outside and immediately before the subroutine)
The passed parameter is the date in DD/MM/YY format. It is parsed and reformatted as YYYY-MM-DD and the subroutine returns the result of comparing it with the two boundary dates
use strict;
use warnings;
use v5.10; # for 'state' variables
use Time::Piece;
use Time::Seconds 'ONE_DAY';
for my $month ( 1 .. 12 ) {
my $date = sprintf '14/%02d/15', $month;
printf "date %s is %s\n", $date, in_range($date) ? 'in range' : 'out of range';
}
sub in_range {
state $ymd_format = '%Y-%m-%d';
state $dmy_format = '%d/%m/%y';
state $now = localtime;
state $today = $now->strftime($ymd_format);
state $days90 = ($now - 90 * ONE_DAY)->strftime($ymd_format);
my $date = Time::Piece->strptime(shift, $dmy_format)->strftime($ymd_format);
$date le $today and $date ge $days90;
}
output
date 14/01/15 is out of range
date 14/02/15 is out of range
date 14/03/15 is out of range
date 14/04/15 is out of range
date 14/05/15 is in range
date 14/06/15 is in range
date 14/07/15 is in range
date 14/08/15 is out of range
date 14/09/15 is out of range
date 14/10/15 is out of range
date 14/11/15 is out of range
date 14/12/15 is out of range
You could use this to get dates into variable i.e split with / and store it in variables:
use strict;
use warnings;
use POSIX qw(strftime);
use Time::Local qw(timegm);
my ($d,$m,$y) = (localtime())[3,4,5];
my ($day90,$month90,$year90) = split(/\//,(strftime("%d/%m/%y", gmtime(timegm(0,0,0,$d,$m,$y)-90*24*60*60))));
my ($day,$month,$year)=split(/\//,(strftime("%d/%m/%y", gmtime(timegm(0,0,0,$d,$m,$y)))));
print "DATE(BEFORE 90 days): $day90 $month90 $year90 \n";
print "DATE(CURRENT): $day $month $year \n";
Output:
DATE(BEFORE 90 days): 11 05 15
DATE(CURRENT): 09 08 15

Find week of a year given the date in mm/dd/yyyy

I am trying to find the week that a date falls in, in a certain year. I have a bunch of files that need to be sorted into folders like "week1-2012" and "week34-2011". I tried searching but a lot of the results aren't really helping because I am currently using perl v5.6.1, super old and I can't download any modules. I also found this link ( How do I calculate the week number given a date?) of interest but was wondering how I would go about getting the day of year and week easily. Was thinking of getting the month, and adding the appropriate amount of days to find out the day in the year. Any help would be appreciated. An example of the year format I am looking for is
//year 2012
S M T W R F S
1 2 3 <-- week #1
4 5 6 7 8 9 10 <-- week #2 //came from the link
//dec year 2011
S M T W T F S
27 28 29 31 <-- week #52 or 53, not to sure the actual week
You can use core modules: POSIX and Time::Local
1.parse your date to (sec, min, hour, mday, month, year)
2.convert your date to seconds (epoch)
3.use function strftime to get week from current date
use strict;
use Time::Local;
use POSIX qw(strftime);
my $date = '08/15/2012';
my ($month, $day, $year) = split '/', $date;
my $epoch = timelocal( 0, 0, 0, $day, $month - 1, $year - 1900 );
my $week = strftime( "%U", localtime( $epoch ) );
printf "Date: %s № Week: %s\n", $date, $week;
OUTPUT
Date: 08/15/2012 № Week: 33
Perl 5.6.1 dates from April 2001. Someone is making your life much harder than it needs to be by not giving you modern tools to use. I suggest it's worth spending some time fixing that problem.
If you had Perl 5.10 or greater, then it would include the Time::Piece module. And your problem would become trivial.
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use Time::Piece;
my $date = '08/15/2012';
my $dt = Time::Piece->strptime($date, '%m/%d/%Y');
say $dt->strftime('week%W-%Y');
Running it gives:
$ ./week
week33-2012
This counts the week as Mon-Sun, not Sun-Sat.
Here's an alternate solution, using DateTime:
use strict;
use warnings;
use DateTime;
my $dt=DateTime->now(time_zone=>"local");
print $dt->week_number . "\n";
The output is, of course:
33
Edit: Of course you can download modules! If nothing else, you can copy and use the relevant code from DateTime.

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 )