Get ISO DateTime with only core modules in Perl? - perl

I would like to get a date-time string such as 2015-06-17 10:20:34 with only core modules. The reason of this is that cpan install DateTime takes ages on my machine because of the tests and the fetch of all the dependencies.
If I give my Program to my colleagues. They will also need to install the missing modules. However, if I simply do:
my $date = `date "+%Y-%m-%d %H:%M:%S"`; chomp $date;
It takes only one line, it requires no additional modules and works on all POSIX machines.
Why should I need to install DateTime in this case?
Yet DateTime->now is the solution recommended by google. I think it shouldn't, I think I am wrong and I can't figure out why.

Perl 5.14 contains the core module Time::Piece, which has a datetime method that returns a date and time in ISO 8601.
use Time::Piece;
my $t = localtime;
say $t->datetime;
This will return 2015-06-17T10:59:15. If you don't want the T, remove it.
say localtime->datetime =~ y/T/ /r;

The Time::Piece module has been in core Perl since version 5.10, about eight years ago
It overrides the localtime core function and looks like this
use Time::Piece;
print localtime->datetime;
output
2015-06-17T12:55:36
If you really want a space instead of the standard T then, on Perl v5.14 or later you can write
print localtime->datetime =~ tr/T/ /r;
In earler versions, which don't have the /r "non-destructive" option, you would have to put the string into a variable and modify it with tr/T/ /

This is simple with the POSIX function strftime:
use POSIX qw( strftime );
print strftime "%Y-%m-%d %H:%M:%S", localtime;
POSIX has been core since 5.0.

Time::Piece gives you an object with many useful methods. As other people have pointed out, the datetime() gives you the date and time in ISO format.
use Time::Piece;
print localtime->datetime;
But if you want a different format (like the "not quite ISO" format in your question) you can use the strftime() method.
use Time::Piece;
print localtime->strftime('%Y-%m-%d %H:%M:%S');
A couple of other points:
Your DateTime installation might go quicker if you used a pre-built package (for example, yum install perl-DateTime on a RedHat derived system).
There are two reasons for avoiding shelling out to run system commands - firstly you lose cross-platform compatibility and secondly creating a new shell is a relatively slow and expensive operation.

Related

How to calculate difference between 2 time stamps in PERL

Code to calculate the difference between 2 given time stamps. Start_Time="2017-09-19 19:36:12.763" End_Time="2017-09-19 19:36:22.72" I want to get the difference between above 2 time stamps also considering the milliseconds.
This is what i tried:
#!/usr/bin/perl
use strict;
use warnings;
use Date::Parse;
use Date::Format;
$startdat = "2007-11-17 12:51:22";
$stopdate = "2007-11-17 12:52:22";
my ($years, $months, $days, $hours, $mins, $secs) = split /\W+/, $startdat;
my ($yeart, $montht, $dayt, $hourt, $mint, $sect) = split /\W+/, $stopdate;
my $times = timelocal($secs,$mins,$hours,$days,$months,$years);
my $timet = timelocal($sect,$mint,$hourt,$dayt,$montht,$yeart);
$time = $timet - $times;
print $time;
But this gives me an error: Can't locate Date/Parse.pm in #INC.
You are getting that error message because Date::Parse isn't installed. This module isn't a part of the standard Perl distribution and needs to be installed separately. If you want to use it, then you need to install it.
However.
Although you use Date::Parse and Date::Format in your code, you don't actually make use of them. So you can just delete those two lines. Then your code will probably work.
Date::Parse and Date::Format are actually quite dated modules. For date and time work in Perl, I recommend Time::Piece (which is a standard part of the Perl library) or DateTime (which needs to be installed).

Getting a Day from Date in Perl

Is there a way to find out day of the week when provided a date. How would I code this in Perl?
For example
Given 02-02-2016(dd-mm-yyyy) outputs Friday
Note: Without using any modules.
Note: Without using any modules.
Calendaring is hard. No, calendaring is REALLY HARD! It's very easy to get wrong. Use a module.
Fortunately there's a built in module to do this, Time::Piece. It has strptime to parse the date into an object. From there you can ask it a great number of things.
use v5.10;
use strict;
use warnings;
use Time::Piece;
my $time = Time::Piece->strptime("02-02-2016", "%d-%m-%Y");
say $time->fullday;

Perl - store output of localtime into hash

In perl I am able to get current seconds using this sequence of commands:
my #time = ($sec,$min,$hour,$day,$mon,$year_1900,$wday,$yday,$isdst)=localtime;
print $time[0]
Is there any equivalent of this but using hashes? So one can type something like this:
print $time{"sec"}
I have tried:
my %time= ("sec","min","hour","day","mon","year_1900","wday","yday","isdst")=localtime;
print $time{"sec"}
But it ended with following error:
Can't modify constant item in list assignment at -e line 1, near "localtime;"
Execution of -e aborted due to compilation errors.
Thanks
Instead of storing everything in a hash, you can store it in an object using Time::Piece:
#!/usr/bin/perl
use strict;
use warnings;
use Time::Piece;
my $t = localtime;
print $t->sec;
Time::Piece can do date math, comparisons, parsing, and output, making it more flexible than a simple hash.
You can use hash slice
my %time;
#time{"sec","min","hour","day","mon","year_1900","wday","yday","isdst"} = localtime;
# or shorter
# #time{qw(sec min hour day mon year_1900 wday yday isdst)} = localtime;
print $time{"sec"};
There are a few standard Perl modules that can help you.
The first is Time::localtime. Thie replaces the internal localtime command with a by name methods for accessing the various parts of the time:
use strict; # Always
use warnings; # Always
use feature qw(say); # An improved version of the print command
use Time::localtime
my $time = localtime; # A Time::Tm object, not your standard localtime command
say $time->sec; # Prints the seconds
This isn't quite the hash you've requested, but you can see that it greatly improves the access to the various pieces provided by localtime in a way that's almost like a hash.
You can also see the use of say which is like print except that you don't need that pesky \n on the end (like you forgot in your example).
Taking things to the next step...
Another nice standard module is called Time::Piece. It provides even easier ways to parse time and to display it.
use strict; # Always
use warnings; # Always
use feature qw(say); # An improved version of the print command
use Time::Piece
my $time = localtime;
say $time->sec; # Prints the seconds. Looks pretty much the same
#
# But now look!
#
say $time->ymd; # Prints the time as YYYY-MM-DD
say $time->ymd("/"); # Prints the time as YYYY/MM/DD
say $time->mdy("/"); # Prints the time as MM/DD/YYYY
say $time->month; # Prints the name of the month
say $time->cdate; # Prints date and time as a string
I prefer Time::Piece because of its flexibility and the ease of initializing a date that's not the current time. If you have a string for the date/time, and you can describe it, you can easily create a new Time::Piece date/time object that can be manipulated.
One of the many things Perl programmers miss is the cornucopia of standard Perl modules that are packed into almost every Perl distribution. These modules do not require downloading or installation. They're there and available on almost any computer that has that particular Perl release.
You can use File::Copy for that missing file copy command. You can use File::Basename for that missing basename and dirname command. You can use the hundreds of modules that come with Perl to make your life easier. Take a tour of the standard Perl documentation and play around with it.

Undefined subroutine &main::timelocal error

I have the following error when I execute my perl module :
Undefined subroutine &main::timelocal
I have defined time and I want in the format of DDMMYYYY without any seperators.
Can anyone help me on this?
To use timelocal like that, you need to import it:
use Time::Local 'timelocal';
(and make sure you are calling it correctly; see Time::Local)
But perhaps you meant localtime? Or you might want POSIX::strftime.
You was not very specific where to get the time. This works for current date, using core Time::Piece module:
use Time::Piece;
print localtime->dmy(''); # 05042011
If you have time in variable, you can do
use Time::Piece;
print localtime($time)->dmy('');
The empty string in dmy call is separator.
The core POSIX module contains a 'strftime' function that handles all of the standard Unix date/time formatting sequences.
$ perl -MPOSIX=strftime -le'print strftime "%d%m%Y", localtime'
Or, in a program,
use POSIX 'strftime';
print strftime '%d%m%Y', localtime, "\n";
It's an old one but I had this exact error and solved it from looking at the above examples with a different solution.
Reason was that I had not included the ';' at the end of the use statement!
use Time::Local ;

How do I convert a date/time to epoch time (unix time/seconds since 1970) in Perl?

Given a date/time as an array of (year, month, day, hour, minute, second), how would you convert it to epoch time, i.e., the number of seconds since 1970-01-01 00:00:00 GMT?
Bonus question: If given the date/time as a string, how would you first parse it into the (y,m,d,h,m,s) array?
If you're using the DateTime module, you can call the epoch() method on a DateTime object, since that's what you think of as unix time.
Using DateTimes allows you to convert fairly easily from epoch, to date objects.
Alternativly, localtime and gmtime will convert an epoch into an array containing day month and year, and timelocal and timegm from the Time::Local module will do the opposite, converting an array of time elements (seconds, minutes, ..., days, months etc.) into an epoch.
This is the simplest way to get unix time:
use Time::Local;
timelocal($second,$minute,$hour,$day,$month-1,$year);
Note the reverse order of the arguments and that January is month 0.
For many more options, see the DateTime module from CPAN.
As for parsing, see the Date::Parse module from CPAN. If you really need to get fancy with date parsing, the Date::Manip may be helpful, though its own documentation warns you away from it since it carries a lot of baggage (it knows things like common business holidays, for example) and other solutions are much faster.
If you happen to know something about the format of the date/times you'll be parsing then a simple regular expression may suffice but you're probably better off using an appropriate CPAN module. For example, if you know the dates will always be in YMDHMS order, use the CPAN module DateTime::Format::ISO8601.
For my own reference, if nothing else, below is a function I use for an application where I know the dates will always be in YMDHMS order with all or part of the "HMS" part optional. It accepts any delimiters (eg, "2009-02-15" or "2009.02.15"). It returns the corresponding unix time (seconds since 1970-01-01 00:00:00 GMT) or -1 if it couldn't parse it (which means you better be sure you'll never legitimately need to parse the date 1969-12-31 23:59:59). It also presumes two-digit years XX up to "69" refer to "20XX", otherwise "19XX" (eg, "50-02-15" means 2050-02-15 but "75-02-15" means 1975-02-15).
use Time::Local;
sub parsedate {
my($s) = #_;
my($year, $month, $day, $hour, $minute, $second);
if($s =~ m{^\s*(\d{1,4})\W*0*(\d{1,2})\W*0*(\d{1,2})\W*0*
(\d{0,2})\W*0*(\d{0,2})\W*0*(\d{0,2})}x) {
$year = $1; $month = $2; $day = $3;
$hour = $4; $minute = $5; $second = $6;
$hour |= 0; $minute |= 0; $second |= 0; # defaults.
$year = ($year<100 ? ($year<70 ? 2000+$year : 1900+$year) : $year);
return timelocal($second,$minute,$hour,$day,$month-1,$year);
}
return -1;
}
To parse a date, look at Date::Parse in CPAN.
I know this is an old question, but thought I would offer another answer.
Time::Piece is core as of Perl 5.9.5
This allows parsing of time in arbitrary formats via the strptime method.
e.g.:
my $t = Time::Piece->strptime("Sunday 3rd Nov, 1943",
"%A %drd %b, %Y");
The useful part is - because it's an overloaded object, you can use it for numeric comparisons.
e.g.
if ( $t < time() ) { #do something }
Or if you access it in a string context:
print $t,"\n";
You get:
Wed Nov 3 00:00:00 1943
There's a bunch of accessor methods that allow for some assorted other useful time based transforms. https://metacpan.org/pod/Time::Piece
$ENV{TZ}="GMT";
POSIX::tzset();
$time = POSIX::mktime($s,$m,$h,$d,$mo-1,$y-1900);
Get Date::Manip from CPAN, then:
use Date::Manip;
$string = '18-Sep-2008 20:09'; # or a wide range of other date formats
$unix_time = UnixDate( ParseDate($string), "%s" );
edit:
Date::Manip is big and slow, but very flexible in parsing, and it's pure perl. Use it if you're in a hurry when you're writing code, and you know you won't be in a hurry when you're running it.
e.g. Use it to parse command line options once on start-up, but don't use it parsing large amounts of data on a busy web server.
See the authors comments.
(Thanks to the author of the first comment below)
My favorite datetime parser is DateTime::Format::ISO8601 Once you've got that working, you'll have a DateTime object, easily convertable to epoch seconds with epoch()
There are many Date manipulation modules on CPAN. My particular favourite is DateTime and you can use the strptime modules to parse dates in arbitrary formats. There are also many DateTime::Format modules on CPAN for handling specialised date formats, but strptime is the most generic.
For further reference, a one liner that can be applied in, for example, !#/bin/sh scripts.
EPOCH="`perl -e 'use Time::Local; print timelocal('${SEC}','${MIN}','${HOUR}','${DAY}','${MONTH}','${YEAR}'),\"\n\";'`"
Just remember to avoid octal values!
Possibly one of the better examples of 'There's More Than One Way To Do It", with or without the help of CPAN.
If you have control over what you get passed as a 'date/time', I'd suggest going the DateTime route, either by using a specific Date::Time::Format subclass, or using DateTime::Format::Strptime if there isn't one supporting your wacky date format (see the datetime FAQ for more details). In general, Date::Time is the way to go if you want to do anything serious with the result: few classes on CPAN are quite as anal-retentive and obsessively accurate.
If you're expecting weird freeform stuff, throw it at Date::Parse's str2time() method, which'll get you a seconds-since-epoch value you can then have your wicked way with, without the overhead of Date::Manip.
I'm using a very old O/S that I don't dare install libraries onto, so here's what I use;
%MonthMatrix=("Jan",0,"Feb",31,"Mar",59,"Apr",90,"May",120,"Jun",151,"Jul",181,"Aug",212,"Sep",243,"Oct",273,"Nov",304,"Dec",334);
$LeapYearCount=int($YearFourDigits/4);
$EpochDayNumber=$MonthMatrix{$MonthThreeLetters};
if ($LeapYearCount==($YearFourDigits/4)) { if ($EpochDayNumber<32) { $EpochDayNumber--; }}
$EpochDayNumber=($YearFourDigits-1970)*365+$LeapYearCount+$EpochDayNumber+$DayAsNumber-493;
$TimeOfDaySeconds=($HourAsNumber*3600)+($MinutesAsNumber*60)+$SecondsAsNumber;
$ActualEpochTime=($EpochDayNumber*86400)+$TimeOfDaySeconds;
The input variables are;
$MonthThreeLetters
$DayAsNumber
$YearFourDigits
$HourAsNumber
$MinutesAsNumber
$SecondsAsNumber
...which should be self-explanatory.
The input variables, of course, assume GMT (UTC). The output variable is "$ActualEpochTime". (Often, I only need $EpochDayNumber, so that's why that otherwise superfluous variable sits on its own.)
I've used this formula for years with nary an error.
Here is a quick example that uses the Perl module Time::Local
use Time::Local;
$number_of_seconds = timelocal(0,24,2, 26,3,2022);
The arguments timelocal needs are: second, minute, hour, day, month, year
A filter converting any dates in various ISO-related formats (and who'd use anything else after reading the writings of the Mighty Kuhn?) on standard input to seconds-since-the-epoch time on standard output might serve to illustrate both parts:
martind#whitewater:~$ cat `which isoToEpoch`
#!/usr/bin/perl -w
use strict;
use Time::Piece;
# sudo apt-get install libtime-piece-perl
while (<>) {
# date --iso=s:
# 2007-02-15T18:25:42-0800
# Other matched formats:
# 2007-02-15 13:50:29 (UTC-0800)
# 2007-02-15 13:50:29 (UTC-08:00)
s/(\d{4}-\d{2}-\d{2}([T ])\d{2}:\d{2}:\d{2})(?:\.\d+)? ?(?:\(UTC)?([+\-]\d{2})?:?00\)?/Time::Piece->strptime ($1, "%Y-%m-%d$2%H:%M:%S")->epoch - (defined ($3) ? $3 * 3600 : 0)/eg;
print;
}
martind#whitewater:~$
If you're just looking for a command-line utility (i.e., not something that will get called from other functions), try out this script. It assumes the existence of GNU date (present on pretty much any Linux system):
#! /usr/bin/perl -w
use strict;
$_ = (join ' ', #ARGV);
$_ ||= <STDIN>;
chomp;
if (/^[\d.]+$/) {
print scalar localtime $_;
print "\n";
}
else {
exec "date -d '$_' +%s";
}
Here's how it works:
$ Time now
1221763842
$ Time yesterday
1221677444
$ Time 1221677444
Wed Sep 17 11:50:44 2008
$ Time '12:30pm jan 4 1987'
536790600
$ Time '9am 8 weeks ago'
1216915200