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.
Related
I am running into issues converting the date Apr 9 2017 3:45:00:000AM to 2017-04-09 03:45:00
Here's what i have tried.
use DateTime::Format::Strptime;
my $strp = DateTime::Format::Strptime->new(pattern => '%h %d %Y %H:%M',);
$start_date = $strp->parse_datetime('Apr 9 2017 3:45:00:000AM');
prints 2017-04-09T03:45:00 and not 2017-04-09 03:45:00. Trying to get 24 hour clock so when i switch AM to PM the same time prints.
Update See end for an strptime pattern to parse the shown input string format correctly
What is shown works,† and so the DateTime::Format::Strptime constructor returns a DateTime object. But when an object is simply printed then one gets the stringification that is shown.
So you need to format it for printing as desired. A general way is with strftime
say $start_date->strftime("%F %T");
where both %F and %T are shorthands, see strftime patterns
Or combine the basic ymd and hms methods
say $start_date->ymd('-') . ' ' . $start_date->hms(':');
See the docs and adjust if/as needed. I didn't understand some details.
† It works only by accident in this exact example, since the pattern given to use for parsing in new is wrong for the shown input format, and is also inconsistent with stated requirements
The shown pattern doesn't have a format specifier for the seconds, nor for the milliseconds that follow it, nor for the following AM/PM -- all expected in the input string. So in general an input in the shown form cannot be parsed correctly with the shown pattern
The %H matches 00-23 hour, so not 12-hour clock which is stated as expected and is implicit by the presence of AM. (It still matches a 12-hour-time number but it won't once the missing AM/PM format specifier is added.)
The pattern in the OP works and parses the given input correctly because 03:45... happens to be in AM, and the module uses regex to match the given pattern anywhere in the given string (by default), so %H:%M matches 03:45 and the rest of the input string doesn't matter. If we turn on strict in the constructor this'll fail. See documentation.
Assuming that the shown input is the correct part we'd need
my $strp = DateTime::Format::Strptime->new(
pattern => '%h %d %Y %I:%M:%S%3N%p'
);
Here %I is for 12-hour clock (1-12), added %S is for seconds and %3N for milliseconds (see the page in docs for patterns, linked above), and %p for AM/PM.
The rest then works as it stands, along with printing in a desired format given above.
Following code demonstrates how desired output can be achieved without any modules
use strict;
use warnings;
use feature 'say';
my $date = 'Apr 9 2017 3:45:00:000AM';
my #fields = qw/month mday year hour min sec usec/;
my %months = ( Jan => 1, Feb => 2, Mar => 3,
Apr => 4, May => 5, Jun => 6,
Jul => 7, Aug => 8, Sep => 9,
Oct => 10, Nov => 11, Dec => 12
);
my %parts;
#parts{#fields} = split "[ :]+", $date;
$parts{hour} += 12 if $parts{usec} =~ /PM/;
$parts{month} = $months{ $parts{month} };
printf "%04d-%02d-%02d %02d:%02d:%02d\n",
#parts{qw/year month mday hour min sec/};
Perl code with assistance of module
use strict;
use warnings;
use feature 'say';
use DateTime::Format::DateParse;
my($date, $dt);
$date = 'Apr 9 2017 3:45:00:000AM';
$date =~ s/:(\d{3}[AP]M)/.$1/;
$dt = DateTime::Format::DateParse->parse_datetime( $date );
$date = $dt;
$date =~ s/T/ /;
say $date;
There are two issues with your code. First, the pattern you use to parse your date is not correct: %H is used for a 24-hour format hour. Instead, you should use a combination of %i (12-hour) and %p (AM/PM). Second, to print a DateTime object, you should first format it first (using ->strftime or ->ymd() for instance).
The milliseconds in the date are, however, a bit of an issue because strptime does not have a option to match milliseconds. I suggest to first remove them from your date, and only then parse the date with strptime:
use DateTime::Format::Strptime;
my $date = 'Apr 9 2017 3:45:00:505PM';
# Removing milliseconds from date
$date =~ s/:(\d{3})(?=AM|PM)//;
my $milliseconds = $1;
my $strp = DateTime::Format::Strptime->new(pattern => '%h %d %Y %I:%M:%S%p',);
my $start_date = $strp->parse_datetime($date);
# Taking into account the milliseconds that were removed earlier
$start_date->add(seconds => 1) if $milliseconds > 500;
say $start_date->strftime("%F %T");
I'm trying to get a customized date and time output using Perl.
Can someone help me to get the output as desired?
#!/usr/bin/perl -w
use POSIX;
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
$year += 1900;
print "$sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst\n";
$now_string = localtime;
print "$now_string\n";
$date = strftime "%a %b %e %H:%M:%S %Y", localtime;
print "Date1 is: $date\n";
$date = strftime "%a-%B-%e", localtime;
print "Date2 is: $date\n";
$time = strftime "%H:%M:%S", localtime;
print "Time1 is: $time\n";
$time1 = strftime "%h:%m:%s", localtime;
print "Time2 is: $time1\n";
output
15, 2, 18, 8, 9, 2017, 0, 280, 0
Sun Oct 8 18:02:15 2017
Date1 is: Sun Oct 8 18:02:15 2017
Date2 is: Sun-October- 8
Time1 is: 18:02:15
Time2 is: Oct:10:1507465935
Desired output:
Date1 is: 06-Oct-2017
Date2 is: 06-10-2017
Time1 is: 23:35:10
Time2 is: 11:35:10 PM
Perl has great built-in help. Start by running the command:
perldoc POSIX
and looking at the strftime section.
Here is an ISO 8601 style timestamp:
schumack#linux2 18> perl -MPOSIX -le 'print(strftime("%Y-%m-%dT%H:%M:%S", localtime))'
2017-10-07T00:11:41
From perldoc POSIX:
"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.
If you want your code to be portable, your format ("fmt") argument
should use only the conversion specifiers defined by the ANSI C
standard (C89, to play safe). These are "aAbBcdHIjmMpSUwWxXyYZ%".
But even then, the results of some of the conversion specifiers
are non-portable. For example, the specifiers "aAbBcpZ" change
according to the locale settings of the user, and both how to set
locales (the locale names) and what output to expect are
non-standard. The specifier "c" changes according to the timezone
settings of the user and the timezone computation rules of the
operating system. The "Z" specifier is notoriously unportable
since the names of timezones are non-standard. Sticking to the
numeric specifiers is the safest route.
The given arguments are made consistent as though by calling
"mktime()" before calling your system's "strftime()" function,
except that the "isdst" value is not affected.
The string for Tuesday, December 12, 1995.
$str = POSIX::strftime( "%A, %B %d, %Y",
0, 0, 0, 12, 11, 95, 2 );
print "$str\n";
Perl has great built-in help. Start by running the command:
perldoc POSIX
and looking at the strftime section.
Here is an ISO 8601 style timestamp:
schumack#linux2 18> perl -MPOSIX -le 'print(strftime("%Y-%m-%dT%H:%M:%S", localtime))'
2017-10-07T00:11:41
Input:
$str="Thu Mar 25 01:48:45 IST 2011";
Desired output:
2011-03-25
I want only date, not the time.
#!/usr/bin/env perl
use strict;
use warnings;
use Time::Piece;
my $tstamp = Time::Piece->strptime
("Thu Mar 25 01:48:45 2011", "%a %b %d %H:%M:%S %Y");
print $tstamp->strftime("%Y-%m-%d\n");
use Date::Manip;
$str =~ s/[[:upper:]]{3}//; # Remove timezone
$d = ParseDate($str);
die "Invalid date\n" unless $d;
$d=~s/(....)(..)(..).*/$1-$2-$3/;
print "$d\n";
Heck, if you know the format of the date, you don't even need to use a Perl module to manipulate the date and time:
my %months = (Jan => 1, Feb => 2, Mar => 3, Apr => 4 ...);
my $st r= "Thu Mar 25 01:48:45 IST 2011";
$st =~! /\S+\s+(\S+)\s+(\S+)\s+\S+\s+\S+(\S+)/;
my $date = sprintf "%s-%02s-%02s", $3, $months{$1}, $2;
Okay, this is very error prone, and you probably want to do a lot of error checking. The regular expression I used could be formatted a bit stronger (checking for characters and numbers instead of just "not white space". And, you probably want to make sure the month is valid too.
Actually, you're better off using a Date/Time module to do this. I was going to recommend Time::Piece, but James_R_Ferguson beat me to it.
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 )
I want to calculate (using the default Perl installation only) the number of days between two dates. The format of both the dates are like so 04-MAY-09. (DD-MMM-YY)
I couldn't find any tutorials that discussed that date format. Should I be building a custom date checker for this format? Further reading of the Date::Calc on CPAN it looks unlikely that this format is supported.
There seems to be quite a bit of confusion because, depending on what you are trying to accomplish, “the number of days between two dates” can mean at least two different things:
The calendar distance between the two dates.
The absolute distance between the two dates.
As an example and to note the difference, assume that you have two DateTime objects constructed as follows:
use DateTime;
sub iso8601_date {
die unless $_[0] =~ m/^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/;
return DateTime->new(year => $1, month => $2, day => $3,
hour => $4, minute => $5, second => $6, time_zone => 'UTC');
}
my $dt1 = iso8601_date('2014-11-04T23:35:42Z');
my $dt2 = iso8601_date('2014-11-07T01:15:18Z');
Note that $dt1 is quite late on a Tuesday, while $dt2 is very early on the following Friday.
If you want the calendar distance use:
my $days = $dt2->delta_days($dt1)->delta_days();
print "$days\n" # -> 3
Indeed, between, Tuesday and Friday there are 3 days. A calendar distance of 1 means “tomorrow” and a distance of -1 means “yesterday”. The “time” part of the DateTime objects is mostly irrelevant (except perhaps if the two dates fall on different time zones, then you would have to decide what “the calendar distance” between those two dates should mean).
If you want the absolute distance then instead use:
my $days = $dt2->subtract_datetime_absolute($dt1)->delta_seconds / (24*60*60);
print "$days\n"; # -> 2.06916666666667
Indeed, if you want to split the time between the two dates in 24-hour chunks, there are only about 2.07 days between them. Depending on your application, you might want to truncate or round this number. The “time” part of the DateTime objects is very relevant, and the expected result is well defined even for dates on different time zones.
If you care about accuracy, keep in mind that not all days have 86400 seconds. Any solution based on that assumption will not be correct for some cases.
Here's a snippet I keep around to calculate and display date/time differences a few different ways using the DateTime library. The last answer printed is the one you want, I think.
#!/usr/bin/perl -w
use strict;
use DateTime;
use DateTime::Format::Duration;
# XXX: Create your two dates here
my $d1 = DateTime->new(...);
my $d2 = DateTime->new(...);
my $dur = ($d1 > $d2 ? ($d1->subtract_datetime_absolute($d2)) :
($d2->subtract_datetime_absolute($d1)));
my $f = DateTime::Format::Duration->new(pattern =>
'%Y years, %m months, %e days, %H hours, %M minutes, %S seconds');
print $f->format_duration($dur), "\n";
$dur = $d1->delta_md($d2);
my $dy = int($dur->delta_months / 12);
my $dm = $dur->delta_months % 12;
print "$dy years $dm months ", $dur->delta_days, " days\n";
print $dur->delta_months, " months ", $dur->delta_days, " days\n";
print $d1->delta_days($d2)->delta_days, " days\n";
Time::ParseDate will handle that format just fine:
use Time::ParseDate qw(parsedate);
$d1="04-MAR-09";
$d2="06-MAR-09";
printf "%d days difference\n", (parsedate($d2) - parsedate($d1)) / (60 * 60 * 24);
Date::Calc has Decode_Date_EU (and US etc)
#!/usr/bin/perl
use Date::Calc qw(Delta_Days Decode_Date_EU);
($year1,$month1,$day1) = Decode_Date_EU('02-MAY-09');
($year2,$month2,$day2) = Decode_Date_EU('04-MAY-09');
print "Diff = " . Delta_Days($year1,$month1,$day1, $year2,$month2,$day2);
This question already has a nice answer, but I want to provide a answer showing why calculating the difference in seconds is WRONG (when we're using formatted/local dates rather than floating dates).
I find it distressing how many suggestions tell people to subtract seconds. (This question was the first Google hit for my search, so I don't care how old it is.)
I've made that mistake myself and wondered why the application would suddenly (over the weekend) show incorrent times. So I'm hoping this code will help people (who may be facing such an issue) understand why this approach is wrong and help them avoid that mistake.
Here is a complete example, one that doesn't contain "..." at some crucial point (because if you insert two dates in the same time zone, you may not see an error).
#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
use DateTime;
# Friday, Oct 31
my $dt1 = DateTime->new(
time_zone => "America/Chicago",
year => 2014,
month => 10,
day => 31,
);
my $date1 = $dt1->strftime("%Y-%m-%d (%Z %z)");
# Monday, Nov 01
my $dt2 = $dt1->clone->set(month => 11, day => 3);
my $date2 = $dt2->strftime("%Y-%m-%d (%Z %z)");
# Friday, Mar 06
my $dt3 = DateTime->new(
time_zone => "America/Chicago",
year => 2015,
month => 3,
day => 6,
);
my $date3 = $dt3->strftime("%Y-%m-%d (%Z %z)");
# Monday, Mar 09
my $dt4 = $dt3->clone->set(day => 9);
my $date4 = $dt4->strftime("%Y-%m-%d (%Z %z)");
# CDT -> CST
print "dt1:\t$dt1 ($date1):\t".$dt1->epoch."\n";
print "dt2:\t$dt2 ($date2):\t".$dt2->epoch."\n";
my $diff1_duration = $dt2->subtract_datetime_absolute($dt1);
my $diff1_seconds = $diff1_duration->seconds;
my $diff1_seconds_days = $diff1_seconds / 86400;
print "diff:\t$diff1_seconds seconds = $diff1_seconds_days days (WRONG)\n";
my $diff1_seconds_days_int = int($diff1_seconds_days);
print "int:\t$diff1_seconds_days_int days (RIGHT in this case)\n";
print "days\t".$dt2->delta_days($dt1)->days." days (RIGHT)\n";
print "\n";
# CST -> CDT
print "dt3:\t$dt3 ($date3):\t".$dt3->epoch."\n";
print "dt4:\t$dt4 ($date4):\t".$dt4->epoch."\n";
my $diff3_duration = $dt4->subtract_datetime_absolute($dt3);
my $diff3_seconds = $diff3_duration->seconds;
my $diff3_seconds_days = $diff3_seconds / 86400;
print "diff:\t$diff3_seconds seconds = $diff3_seconds_days days (WRONG)\n";
my $diff3_seconds_days_int = int($diff3_seconds_days);
print "int:\t$diff3_seconds_days_int days (WRONG!!)\n";
print "days\t".$dt4->delta_days($dt3)->days." days (RIGHT)\n";
print "\n";
Output:
dt1: 2014-10-31T00:00:00 (2014-10-31 (CDT -0500)): 1414731600
dt2: 2014-11-03T00:00:00 (2014-11-03 (CST -0600)): 1414994400
diff: 262800 seconds = 3.04166666666667 days (WRONG)
int: 3 days (RIGHT in this case)
days 3 days (RIGHT)
dt3: 2015-03-06T00:00:00 (2015-03-06 (CST -0600)): 1425621600
dt4: 2015-03-09T00:00:00 (2015-03-09 (CDT -0500)): 1425877200
diff: 255600 seconds = 2.95833333333333 days (WRONG)
int: 2 days (WRONG!!)
days 3 days (RIGHT)
Notes:
Again, I'm using local dates. If you use floating dates, you won't have that problem - simply because your dates stay in the same time zone.
Both time ranges in my example go from friday to monday, so the difference in days is 3, not 3.04... and of course not 2.95...
Turning the float into an integer using int() (as suggested in an answer) is just wrong, as shown in the example.
I do realize that rounding the difference in seconds would also return correct results in my example, but I feel like it's still wrong. You'd be calculating a day difference of 2 (for a large value of 2) and, because it is a large value of 2, turn it into a 3. So as long as DateTime provides the functionality, use DateTime.
Quoting the documentation (delta_days() vs subtract_datetime()):
date vs datetime math
If you only care about the date (calendar) portion of a datetime, you
should use either delta_md() or delta_days(), not subtract_datetime().
This will give predictable, unsurprising results, free from
DST-related complications.
Bottom line: Don't diff seconds if you're using DateTime. If you're not sure what date framework to use, use DateTime, it's awesome.
You could convert the dates into the long integer format, which is the number of seconds since the epoch (some date in 1970 I think). You then have two variables that are the dates in seconds; subtract the smaller from the larger. Now you have a time span in seconds; divide it by the number of seconds in 24 hours.
Convert the two dates to seconds and then do the math:
#!/usr/bin/perl
use strict;
use warnings;
use POSIX qw/mktime/;
{
my %mon = (
JAN => 0,
FEB => 1,
MAR => 2,
APR => 3,
MAY => 4,
JUN => 5,
JUL => 6,
AUG => 7,
SEP => 8,
OCT => 9,
NOV => 10,
DEC => 11,
);
sub date_to_seconds {
my $date = shift;
my ($day, $month, $year) = split /-/, $date;
$month = $mon{$month};
if ($year < 50) { #or whatever your cutoff is
$year += 100; #make it 20??
}
#return midnight on the day in question in
#seconds since the epoch
return mktime 0, 0, 0, $day, $month, $year;
}
}
my $d1 = "04-MAY-99";
my $d2 = "04-MAY-00";
my $s1 = date_to_seconds $d1;
my $s2 = date_to_seconds $d2;
my $days = int(($s2 - $s1)/(24*60*60));
print "there are $days days between $d1 and $d2\n";