check if date is valid - perl

In my program I have the user input a date in the format of "Month Day" (example May 25) and I want to be able to print an error message if it is an invalid date (example February 30).
So here's some code
$start_date = ARGV[0];
my $year = DateTime->now->year; # adds a year to the date
my $date_parser = DateTime::Format::Strptime->new(pattern => '%Y %B %d', # YYYY Month DD
);
my $start_epoch = $date_parser->parse_datetime("$year $start_date")->epoch();
Right after this I need some kind of if statement right?

If the date is invalid, then the parser will return undef. You will see this quite quickly if you do:
my $start_date = "Feb 30";
my $year = DateTime->now->year; # adds a year to the date
my $date_parser = DateTime::Format::Strptime->new(pattern => '%Y %B %d', # YYYY Month DD
);
my $start_epoch = $date_parser->parse_datetime("$year $start_date")->epoch();
The solution:
my $parsed = $date_parser->parse_datetime("$year $start_date");
if ( not defined $parsed ) { print "Error - invalid date\n"; }

From perlmonks:
use Time::Local;
my $date = ' 19990230'; # 30th Feb 1999
$date =~ s/\s+$//;
$date =~ s/^\s*//;
my ($year, $month, $day) = unpack "A4 A2 A2", $date;
eval{
timelocal(0,0,0,$day, $month-1, $year); # dies in case of bad date
+
1;
} or print "Bad date: $#";
This should do you justice

Related

How can I change date to swedish time zone in a Perl script?

How can I convert a date in format '20170119121941Z' to Swedish time zone in a Perl script?
My current snippet is:
sub get_Date {
my $content = shift;
my $iso8601 = DateTime::Format::ISO8601 -> new;
my $dt = $iso8601->parse_datetime( $content );
###Set the time zone to "Europe/Stockholm" .
$dt->set_time_zone("Europe/Stockholm");
my $dayofmonth = $dt->strftime("%d");
$dayofmonth =~ s/^0//;
my $hour = $dt->strftime("%I");
$hour =~ s/^0//;
my $ISODate = $dt->strftime("%b " . $dayofmonth . ", %Y, " . $hour . ":%M %p ", localtime(time));
return($ISODate);
}
The output you are getting is
Invalid date format: 20170119121941Z
Your code is failing with that message because 20170119121941Z doesn't match a valid ISO8601 format.
There's also the issue that you used strftime correctly twice, then did something nonsense for the third use.
Solution:
use strict;
use warnings;
use feature qw( say state );
use DateTime::Format::Strptime qw( );
sub localize_dt_str {
my ($dt_str) = #_;
state $format = DateTime::Format::Strptime->new(
pattern => '%Y%m%d%H%M%S%Z',
on_error => 'croak',
);
my $dt = $format->parse_datetime($dt_str);
$dt->set_time_zone('Europe/Stockholm');
return $dt->strftime('%b %e, %Y, %l:%M %p');
}
say localize_dt_str('20170119121941Z'); # Jan 19, 2017, 1:19 PM

Convert Old Unix Date to Perl and compare

Requirement - I have file name called "Rajesh.1202242219". Numbers are nothing but a date "date '+%y''%m''%d''%H''%M'" format.
Now I am trying to write a perl script to extract the numbers from file name and compare with current system date and time and based on output of this comparison, print some value using perl.
Approach:
Extract the Digit from File name:
if ($file =~ /Rajesh.(\d+).*/) {
print $1;
}
Convert this time into readable time in perl
my $sec = 0; # Not Feeded
my $min = 19;
my $hour = 22;
my $day = 24;
my $mon = 02 - 1;
my $year = 2012 - 1900;
my $wday = 0; # Not Feeded
my $yday = 0; # Not Feeded
my $unixtime = mktime ($sec, $min, $hour, $day, $mon, $year, $wday, $yday);
print "$unixtime\n";
my $readable_time = localtime($unixtime);
print "$readable_time\n";
find Current time and compare...
my $CurrentTime = time();
my $Todaydate = localtime($startTime);
But the problem here is, I am not getting solution of how to extract 2 digit from $1 and assign to $sec, $min, etc. Any help?
Also, if you have good approach for this problem statement, Please share with me
I like to use time objects to simplify the logic. I use Time::Piece here because it is simple and light weight (and part of the core). DateTime can be another choice.
use Time::Piece;
my ( $datetime ) = $file =~ /(\d+)/;
my $t1 = Time::Piece->strptime( $datetime, '%y%m%d%H%M' );
my $t2 = localtime(); # equivalent to Time::Piece->new
# you can do date comparisons on the object
if ($t1 < $t2) {
# do something
print "[$t1] < [$t2]\n";
}
Might as well teach DateTime::Format::Strptime to make the comparison much simpler:
use DateTime qw();
use DateTime::Format::Strptime qw();
if (
DateTime::Format::Strptime
->new(pattern => '%y%m%d%H%M')
->parse_datetime('Rajesh.1202242219')
< DateTime->now
) {
say 'filename timestamp is earlier than now';
} else {
say 'filename timestamp is later than now';
};
my ($year, $month, $day, $hour, $min) = $file =~ /(\d{2})/g;
if ($min) {
$year += 100; # Assuming 2012 and not 1912
$month--;
# Do stuff
}
I think unpack might be a better fit.
if ( my ( $num ) = $file =~ /Rajesh.(\d+).*/ ) {
my ( $year, $mon, $day, $hour, $min ) = unpack( 'A2 A2 A2 A2 A2', $num );
my $ts = POSIX::mktime( 0, $min, $hour, $day, $mon - 1, $year + 100 );
...
}
Using a module that parses dates might be nice. This code will parse the date and return a DateTime object. Refer to the documentation to see the many ways to manipulate this object.
use DateTime::Format::Strptime;
my $date = "1202242219";
my $dt = get_obj($date);
sub get_obj {
my $date = shift;
my $strp = DateTime::Format::Strptime->new(
pattern => '%y%m%d%H%M'
);
return $strp->parse_datetime($date);
}

Perl - How to convert a date?

How to convert date format YYYY-MM-DDTHH:MM:SSZ to YYYY-MM-DD HH:MM + 8 hours?
For example:
Input: 2011-07-07T18:05:45Z
Output: 2011-07-08 02:05
Let's start with Rahul's snippet, and add in the date math and output formatting...
use DateTime;
use DateTime::Format::ISO8601;
use DateTime::Format::Strptime;
my $string = '2011-07-07T18:05:45Z';
my $dt = DateTime::Format::ISO8601->parse_datetime( $string );
die "Impossible time" unless $dt;
my $formatter = new DateTime::Format::Strptime(pattern => '%Y-%m-%d %T');
$dt->add( hours => 8 )->set_formatter($formatter);
print "$dt\n";
I've added the use of DateTime::Format::Strptime, in order to specify the desired output format.
Then I've added three more lines:
First I create a formatter, and feed it the output pattern I desire.
Next I add eight hours to the original date, and I assign the output
formatter by chaining the set_formatter() call to the add() call.
Then I print it.
Are you using the DateTime modules?
Specifically, here's a link to DateTime::Format::ISO8601 that reads/writes ISO 8601 format you mentioned as your input.
If you don't have DateTime, you surely have Time::Piece:
use strict;
use warnings;
use Time::Piece;
use Time::Seconds qw(ONE_HOUR);
my $str = '2011-07-07T18:05:45Z';
my $t = Time::Piece->strptime($str, "%Y-%m-%dT%TZ");
$t += 8 * ONE_HOUR;
print $t->strftime("%Y-%m-%d %H:%M"),"\n";
Taken From
How can I validate a "yyyy-MM-dd'T'HH:mm:ssZ" date/timestamp in UTC with Perl?
use DateTime;
use DateTime::Format::ISO8601;
my $string = '2010-02-28T15:21:33Z';
my $dt = DateTime::Format::ISO8601->parse_datetime( $string ); die "Impossible time" unless $dt;
It doesn't work, result is 2010-02-28T15:21:33
Then, do it the hard way...
use Time::Local
use warnings;
use strict;
$time = '2010-02-28T15:21:33Z';
my ($year, month, day) = split (/-/, $time)
$year -= 1900; #Year is an offset of 1900
$month -= 1; #Months are 0 - 11
#Now split the time off of the day (DDTHH:MM:SS)
$day = substr($day, 0, 2);
time = substr($day, 3)
#Now split the time
(my $hour, $minute, $second) = split(/:/, $time);
$second =~ s/Z$//; #Remove Z
my $time_converted = timelocal($second, $minute, $hour, $day, $month, $year);
#Now you have the time, Add eight hours
my $hours_in_seconds = 8 * 60 * 60;
$time_converted += $hours_in_seconds;
# Almost done: Convert time back into the correct array:
($second, $minute, $hour, $day, $month, $year) = localtime($time_converted);
$year += 1900;
$month += 1;
# Now, reformat:
my $formatted_time = sprint (%04d-%02d-%02d %02d:%02d),
$year, $month, $day, $hour, $minute;

Date Conversion

In perl, how do I convert date like
Thu Mar 06 02:59:39 +0000 2008
to
2008-03-06T02:59:39Z
Tried HTTP::Date, it works if the question did not have +0000 in the string :(
DateTime::Format::Strptime will do this conversion.
#!/usr/bin/perl
use strict;
use warnings;
use 5.012;
use DateTime::Format::Strptime;
my $date = 'Thu Mar 06 02:59:39 +0000 2008 ';
my( #strp ) = (
DateTime::Format::Strptime->new( pattern => "%a %b %d %T %z %Y", ),
DateTime::Format::Strptime->new( pattern => "%FY%T%Z", )
);
my $dt = $strp[0]->parse_datetime( $date );
print $strp[1]->format_datetime( $dt );
prints 2008-03-06T02:59:39UTC
Chris
If you're absolutely, positively sure that the date will ALWAYS be in that format, you can simply use regular expressions to reformat it. The only thing is that you have to have a way of converting the month to a number. That way, you don't have to download any extra modules to do the date conversion:
my $date = "Thu Mar 06 02:59:39 +0000 2008"; #Original String
#Create the Month Hash (you might want all twelve months).
my %monthHash (Jan => "01", Feb => 2, Mar => 3);
# Use RegEx Matching to parse your date.
# \S+ means one or more non-spaces
# \s+ means one or more spaces
# Parentheses save that part of the string in $1, $2, $3, etc.
$date =~ m/\S+\s+(\S+)\s+(\S+)\s+(\S+)\s+\S+\s(.*)/;
my $monthString = $1;
my $day = $2;
my $time = $3;
my $year = $4;
# Convert Month string to a number.
my $month = $monthHash{$monthString};
#Reformat the string
$fmtDate="$year-$month-$day" . "T" . "$time" . "Z";
Otherwise I was going to say you can also try DateTime::Format::Strptime, but Chris Charley beat me to it.
So, edit it with a regex and use HTTP::Date:
( my $new_date_string = $old_state_string ) =~ s/[+-]\d{4,}\s+//;

How can I get this week's dates in Perl?

I have the following loop to calculate the dates of the current week and print them out. It works, but I am swimming in the amount of date/time possibilities in Perl and want to get your opinion on whether there is a better way. Here's the code I've written:
#!/usr/bin/env perl
use warnings;
use strict;
use DateTime;
# Calculate numeric value of today and the
# target day (Monday = 1, Sunday = 7); the
# target, in this case, is Monday, since that's
# when I want the week to start
my $today_dt = DateTime->now;
my $today = $today_dt->day_of_week;
my $target = 1;
# Create DateTime copies to act as the "bookends"
# for the date range
my ($start, $end) = ($today_dt->clone(), $today_dt->clone());
if ($today == $target)
{
# If today is the target, "start" is already set;
# we simply need to set the end date
$end->add( days => 6 );
}
else
{
# Otherwise, we calculate the Monday preceeding today
# and the Sunday following today
my $delta = ($target - $today + 7) % 7;
$start->add( days => $delta - 7 );
$end->add( days => $delta - 1 );
}
# I clone the DateTime object again because, for some reason,
# I'm wary of using $start directly...
my $cur_date = $start->clone();
while ($cur_date <= $end)
{
my $date_ymd = $cur_date->ymd;
print "$date_ymd\n";
$cur_date->add( days => 1 );
}
As mentioned, this works, but is it the quickest or most efficient? I'm guessing that quickness and efficiency may not necessarily go together, but your feedback is very appreciated.
A slightly improved version of friedo's answer ...
my $start_of_week =
DateTime->today()
->truncate( to => 'week' );
for ( 0..6 ) {
print $start_of_week->clone()->add( days => $_ );
}
However, this assumes that Monday is the first day of the week. For Sunday, start with ...
my $start_of_week =
DateTime->today()
->truncate( to => 'week' )
->subtract( days => 1 );
Either way, it's better to use the truncate method than re-implement it, as friedo did ;)
You can use the DateTime object to get the current day of the week as a number ( 1-7 ). Then just use that to find the current week's Monday. For example:
my $today = DateTime->now;
my $start = $today->clone;
# move $start to Monday
$start->subtract( days => ( $today->wday - 1 ) ); # Monday gives 1, so on monday we
# subtract zero.
my $end = $start->clone->add( days => 7 );
The above is untested but the idea should work.
Would this work:
use strict;
use warnings;
use POSIX qw<strftime>;
my ( $day, $pmon, $pyear, $wday ) = ( localtime )[3..6];
$day -= $wday - 1; # Get monday
for my $d ( map { $day + $_ } 0..6 ) {
print strftime( '%A, %B %d, %Y', ( 0 ) x 3, $d, $pmon, $pyear ), "\n";
}
I'm printing them only as an illustration. You could store them as timestamps, like this:
use POSIX qw<mktime>;
my #week = map { mktime(( 0 ) x 3, $day + $_, $pmon, $pyear ) } 0..6;
This should work:
use POSIX; # for strftime
my $time = time ();
my $seconds = 24*60*60;
my #time = gmtime ();
$time = $time - $time[6] * $seconds;
for my $wday (0..6) {
$time += $seconds;
my #wday = gmtime ($time);
print strftime ("%A %d %B %Y\n", #wday);
}
Gives me:
$ ./week.pl
Monday 24 May 2010
Tuesday 25 May 2010
Wednesday 26 May 2010
Thursday 27 May 2010
Friday 28 May 2010
Saturday 29 May 2010
Sunday 30 May 2010
If you want to get weeks starting on Sunday, change $time[6] to ($time[6] + 1).
This assumes you want the GMT weeks. Change gmtime to localtime to get local time zone weeks.