This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
In Perl, how can find the date of the previous Monday for a given date?
In the perl, how to write a function to get last Thursday's date? Today (11/21), I want to get 11/17. How can I figure out if a given date is a Sunday or not?
Seems like a work for DateTime (which is the module usually recommended to manipulate dates in Perl). This small example should answer both your questions:
use strict;
use warnings;
use DateTime;
my $d = DateTime->now;
while ( $d->day_of_week != 4 ) {
$d->subtract( days => 1 );
}
print $d->ymd;
Other posts use DateTime too, but they have issues this post addresses.
use DateTime qw( );
my $dt = DateTime->today( time_zone => 'local' );
$dt->subtract( days => ($dt->day_of_week - 4) % 7 );
say $dt->ymd(''); # 20111117
Issues addressed:
Uses the local date instead of the date in or near England.
Uses today instead of now since you're only dealing with dates.
Avoids needless loops.
Note:
Returns the current date is today is a Thursday. You accepted an answer that did the same, so I presume that's what you want.
Update: The above can fail on certain days for certain time zones. (Not all days have a midnight!) Solution:
use DateTime qw( );
my $dt = DateTime->now( time_zone => 'local' );
$dt->set_time_zone('floating');
$dt->truncate( to => 'days' );
$dt->subtract( days => ($dt->day_of_week - 4) % 7 );
say $dt->ymd(''); # 20111117
I'd use what larsen wrote, although reading the localtime reference won't hurt you. You could do something like this to check if its Sunday:
($second, $minute, $hour, $dayOfMonth, $month, $yearOffset,
$dayOfWeek, $dayOfYear, $daylightSavings) = localtime();
if ($dayOfWeek == 0) print("Don't go to work!");
If all you wanted to test was the day-of-the-week an array slice of the localtime() works nicely:
print q(Today is Sunday) if ((localtime)[6]==0)
You can also use Date::Calc:
How do I calculate the last and the next Saturday for any given date?
use Date::Calc qw( Today Day_of_Week Add_Delta_Days
Day_of_Week_to_Text Date_to_Text );
$searching_dow = 6; # 6 = Saturday
#today = Today();
$current_dow = Day_of_Week(#today);
if ($searching_dow == $current_dow)
{
#prev = Add_Delta_Days(#today,-7);
#next = Add_Delta_Days(#today,+7);
}
else
{
if ($searching_dow > $current_dow)
{
#next = Add_Delta_Days(#today,
$searching_dow - $current_dow);
#prev = Add_Delta_Days(#next,-7);
}
else
{
#prev = Add_Delta_Days(#today,
$searching_dow - $current_dow);
#next = Add_Delta_Days(#prev,+7);
}
}
$dow = Day_of_Week_to_Text($searching_dow);
print "Today is: ", ' ' x length($dow),
Date_to_Text(#today), "\n";
print "Last $dow was: ", Date_to_Text(#prev), "\n";
print "Next $dow will be: ", Date_to_Text(#next), "\n";
use Date::Calc qw(:all);
($week, $year) = Week_of_Year(Today());# Week_of_Year(2011,11,21);
print Date_to_Text(Add_Delta_Days(Monday_of_Week($week - 1,$year),3));#THU - MON = 3
#output:Thu 17-Nov-2011
#isSunday?
Day_of_Week(2011,11,20) == 7 # 7 is Sunday enum code(from 1 : Monday)
With just localtime and the POSIX base module, you can do this:
my #ltime = localtime();
my $t
= POSIX::mktime(( 0 ) x 3
, $ltime[3] - ( 7 - 4 + $ltime[6] )
, #ltime[4,5]
);
This will always give you the last Thursday before the last Sunday. So if the day is Saturday, it will give you the previous week's Thursday, but if it's Sunday, it will give you the last past Thursday.
If you prefer the last past Thursday computation, you might do this:
my $t
= POSIX::mktime(( 0 ) x 3
, $ltime[3] - $ltime[6] + ( $ltime[6] > 4 ? 4 : -3 )
, #ltime[4,5]
);
Date::Simple has a pretty clean interface.
#!/usr/bin/perl
use strict;
use warnings;
use Date::Simple qw/ today /;
my $d = today;
# If today is already Thurs, subtract 1 before searching for prior Thursday.
# If today is Thurs and you want to capture that (and not Thur a week ago),
# don't subtract the 1 from $d.
$d--;
$d-- while $d->day_of_week != 4;
print $d->strftime("%Y%m%d");
Related
All,
I want to find out the date of previous wednesday from the given date.
Eg. I have date as "20150804" and i would need "20150729".
DateTime is not available and i cannot install it as well.
I looked at few examples but they were using DateTime.
Can you please redirect me where i can get some help.? Thanks.
I am planning to code something like below.
Code:
#!/opt/perl-5.8.0/bin/perl
use warnings;
use strict;
my $dt="20150804";
my $prevWednesday=getPrevWednesday($dt);
sub getPrevWednesday()
{
my $givenDt=shift;
...
}
Another brute force approach, this time using another core module Time::Local.
#!/usr/bin/perl
use warnings;
use strict;
use Time::Local;
sub prev_wednesday {
my $date = shift;
my ($year, $month, $day) = $date =~ /(....)(..)(..)/;
my $time = timelocal(0, 0, 12, $day, $month - 1, $year);
do { $time -= 60 * 60 * 24 } until (localtime $time)[6] == 3; # <- Wednesday
my ($y, $m, $d) = (localtime $time)[5, 4, 3];
return sprintf "%4d%02d%02d\n", 1900 + $y, $m + 1, $d;
}
print $_, ' ', prev_wednesday($_), for qw( 20150804 20150805 20150806
20150101 20000301 20010301 );
Using Time::Piece :
use feature qw(say);
use strict;
use warnings;
use Time::Piece;
use Time::Seconds;
my $str = '20150804';
my $fmt = '%Y%m%d';
my $t = Time::Piece->strptime($str, $fmt);
do {
$t = $t - ONE_DAY;
} until ( $t->day eq 'Wed');
say $t->strftime($fmt);
There's always the brute force approach.
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use POSIX 'strftime';
my $ONE_DAY = 24 * 60 * 60;
# Get now
my $time = time;
# Subtract days until you get to a Wednesday
do {
$time -= $ONE_DAY;
} until (localtime($time))[6] == 3;
# Format
say strftime '%Y%m%d', localtime $time;
But if you're working in a Perl environment where you can't install modules from CPAN, then it is always worth working to get that restriction removed. Modern Perl programming is often a case of plumbing together the right series of CPAN modules. If you don't have access to CPAN then you're just making your life much harder than it needs to be.
If you really can't get the restriction lifted, then look for another job. It's not worth dealing with people who impose such pointless restrictions.
Update: Just noticed that you're also using a prehistoric version of Perl. You'll need to remove the use 5.010 and replace the say with print. And brush up your CV :-/
Update 2: choroba's solution is better. It deals with any date in the correct format. Mine just deals with the current date. The advice about fixing your working environment still holds though.
Here is a more elegant solution that does not do bruteforce.
use strict;
use warnings;
use Time::Local 'timelocal';
use POSIX 'strftime';
my $dt = "20150804";
say getPrevWednesday($dt);
# note you do not want () here,
# see http://perldoc.perl.org/perlsub.html#Prototypes
sub getPrevWednesday {
my $givenDt = shift;
# parse the string into a unix timestamp
my ( $year, $month, $day ) = $givenDt =~ /(....)(..)(..)/;
my $timestamp = timelocal( 0, 0, 12, $day, $month - 1, $year );
# get the day of week, ignore the rest
my ( undef, undef, undef, undef, undef, undef, $wday ) =
localtime $timestamp;
# because we start the week with Sunday on day 0
# and to get to the previous Wednesday from Sunday it's
# 4 days (Wednesday is 3) we can add 4 to the
# number of this day, divide by 7, take the leftover (modulo)
# and then subtract that many days
# (86_400 is one day in seconds)
# v- -6 ------
# 6 % 7 = 6
# +4 -----v
# v
# 0 1 2 3 4 5 6 0 1 2 3 4 5 6
# S M T W T F S S M T W T F S
my $prev_wed = $timestamp - ( ( $wday + 4 ) % 7 * 86_400 );
# go one week back if we got the same day
$prev_wed -= ( 7 * 86_400 ) if $prev_wed == $timestamp;
# debug output
warn "in: " . localtime($timestamp) . "\n";
warn "out: " . localtime($prev_wed) . "\n\n";
# put it back into your format
return strftime('%Y%m%d', localtime $timestamp);
}
Output:
# STDOUT
20150804
# STDERR
in: Tue Aug 4 12:00:00 2015
out: Wed Jul 29 12:00:00 2015
Is there a way to figure out the first day of the month (min day) and the last day of a month (max day), given the month as input, using DateTime in perl?
So far, I figured out how to pass in a first date, last date to give me a range of days.
But what I want to do now is just pass in a month as an argument, say 201203 and return min, maxday.
Is that possible with DateTime?
Also, I want to change the date format mask from YYYYMMDD to YYYY-MM-DD.
use strict;
use warnings;
use DateTime;
unless(#ARGV==2)
{
print "Usage: myperlscript first_date last_date\n";
exit(1);
}
my ($first_date,$last_date)=#ARGV;
my $date=DateTime->new(
{
year=>substr($first_date,0,4),
month=>substr($first_date,4,2),
day=>substr($first_date,6,2)
});
while($date->ymd('') le $last_date)
{
print $date->ymd('') . "\n";
#$date->add(days=>1); #every day
$date->add(days=>30);
}
Expected Results:
2012-03-01
2012-03-31
DateTime does date math for you. You can tell ymd which character you want to use as the separator:
use DateTime;
my( $year, $month ) = qw( 2012 2 );
my $date = DateTime->new(
year => $year,
month => $month,
day => 1,
);
my $date2 = $date->clone;
$date2->add( months => 1 )->subtract( days => 1 );
say $date->ymd('-');
say $date2->ymd('-');
There are many examples in "Last day of the month. Any shorter" on Perlmonks, which I found by Googling "perl datetime last day of month".
And here's a Time::Moment example. It's a leaner, faster subset of DateTime:
use v5.10;
use Time::Moment;
my( $year, $month ) = qw( 2012 2 );
my $tm = Time::Moment->new(
year => $year,
month => $month,
day => 1,
);
my $tm2 = $tm->plus_months( 1 )->minus_days( 1 );
say $tm->strftime('%Y-%m-%d');
say $tm2->strftime('%Y-%m-%d');
It is surprising, that neither DateTime example uses the special constructor last_day_of_month for that (example courtesy of brian d foy):
use DateTime;
use strict;
use 5.010;
my( $year, $month ) = qw( 2012 2 );
my $date = DateTime->new(
year => $year,
month => $month,
day => 1,
);
my $date2 = DateTime->last_day_of_month(
year => $date->year,
month => $date->month,
);
say $date->ymd('-');
say $date2->ymd('-');
As an alternative there is the core Perl module Time::Piece.
For the current month and year:
perl -MTime::Piece -wE '$t=localtime;say $t->month_last_day'
31
More generally, something like:
use 5.010;
use Time::Piece;
my $MY = shift || die "Month and Year expected\n";
my $t = Time::Piece->strptime($MY, "%m%Y");
say $t->month_last_day;
$ ./mycode 022012
29
First day:
$dt->set_day(1);
Last day:
$dt->set_day(1)->add( months => 1 )->subtract( days => 1 );
While the DateTime class offers a constructor to do this, the most efficient way to get the last day of the month from an existing DateTime object is to request the object to calculate it. The class exposes an undocumented method _month_length() that calculates the last day of the month very efficiently. With your DateTime object called $date, you can try
$date->_month_length($date->year,$date->month);
This method is undocumented, so it may or may not be supported in your version of DateTime. Use with caution.
UPD
Starting from v1.144 DateTime starts to support month_lenth
History of the issue
Using the excellent Perl DateTime module it is trivial to obtain the year and week number for a date, but going the other way seems to be a bit more difficult. How does one go about obtaining a date starting with the year and week number?
Here's one way to do it using only DateTime:
use DateTime;
sub first_day_of_week
{
my ($year, $week) = #_;
# Week 1 is defined as the one containing January 4:
DateTime
->new( year => $year, month => 1, day => 4 )
->add( weeks => ($week - 1) )
->truncate( to => 'week' );
} # end first_day_of_week
# Find first day of second week of 2012 (2012-01-09):
my $d = first_day_of_week(2012, 2);
print "$d\n";
Try:
use Date::Calc qw(:all);
my $year = 2012;
my $week = 14;
my ($year2, $month, $day) = Monday_of_Week($week, $year);
I thought this was going to be very simple but I am really out of options now. I want to substract 15 minutes from a given time.
Example
My time is 15:04 I want to substract 15 minutes to be 14:49. I have searched for solutions on the internet but there is no perl module that can help me out.
You can use DateTime:
my $dt = DateTime->new(
year => 1,
month => 1,
day => 1,
hour => 15,
minute => 4,
);
$dt->subtract(minutes => 15);
printf "%d:%d\n", $dt->hour, $dt->minute; # prints 14:49
Well it all depends on how your time is stored. I prefer to use a time_t as returned by the time built in.
my $now = time();
my $before1 = $now - (15*60); # 15 minutes ago
my $before2 = $now - (3*60*60); # 3 hours ago
my $before3 = $now - (2*24*60*60); # 2 days ago
For output I use the POSIX module
print POSIX::strftime( '%Y-%m-%d %T', localtime($before1) );
perl -MClass::Date -e 'my $d=Class::Date->new("2011-07-13 15:04:00"); my $d2 = $d-"15m"; print $d2, "\n";'
Output:
2011-07-13 14:49:00
Try using Date::Calc
use Date::Calc qw(Add_Delta_DHMS);
($year2, $month2, $day2, $h2, $m2, $s2) =
Add_Delta_DHMS( $year, $month, $day, $hour, $minute, $second, $days_offset, $hour_offset, $minute_offset, $second_offset );
($y,$m,$d,$H,$M,$S) = Add_Delta_DHMS(Today_and_Now(), 0, 0, -15, 0);
convert the time to unix time, for example the current time: $unixtime = time(); then subtract 15*60 from it then convert to a nice string with something like
sub display_time {
my ($sec,$min,$hour,$mday,$mon,$year,undef,undef,undef) = localtime(time);
$year += 1900;
$mon += 1;
return "$year.".sprintf("%02d.%02d %02d:%02d:%02d",$mon,$mday,$hour,$min,$sec);
}
You can use the below sub-routine if you are only concerned about time not date:
sub subTime{
my ($time) = #_;
my #splittime = split(':', $time);
my $hour = $splittime[0];
my $min = $splittime[1];
if($min < 15){
$min=($min+60)-15;
$hour-=1;
}
else{
$min = $min-15;
}
return "$hour:$min";
}
Disclamer: This was the solution OP used, he mentioned it in comments in above answer (in #eugene's answer).
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.