Reputation: 235
I'm working with DateTime objects in order to traverse through a weeks worth of dates. I have a start and end date, and I'm trying to assign the start date to another variable name so I can work with that while not touching the original start date object. But when I add 1 to the start date the variable is adding 1 as well, even though I have it assigned before adding the day. Here is an example of code:
use strict;
use warnings;
use DateTime::Format::Strptime;
my $dt = DateTime->now(time_zone=> 'America/New_York');
my $dateback0 = $dt->strftime('%Y%m%d');
my $dtback7 = DateTime->now(time_zone=> 'America/New_York')->subtract(days => 7);
my $dateback7 = $dtback7->strftime('%Y%m%d');
my $parseseq = DateTime::Format::Strptime->new(pattern => '%Y%m%d');
my $sdate = $dateback7;
my $edate = $dateback0;
$sdate = $parseseq->parse_datetime($sdate);
$edate = $parseseq->parse_datetime($edate);
$sdate->set_formatter($parseseq);
$edate->set_formatter($parseseq);
while($sdate <= $edate) {
my $watermark = $sdate;
print "\nBefore Adding Day\n";
print "watermark $watermark\nsdate $sdate\nedate $edate\n";
$sdate->add(days => 1);
print "\nAfter Adding Day\n";
print "watermark $watermark\nsdate $sdate\nedate $edate\n";
}
So instead of printing out the starting watermark of 20200421 it is printing 20200422.
Before Adding Day
watermark 20200421
sdate 20200421
edate 20200428
After Adding Day
watermark 20200422
sdate 20200422
edate 20200428
Before Adding Day
watermark 20200422
sdate 20200422
edate 20200428
After Adding Day
watermark 20200423
sdate 20200423
edate 20200428
Before Adding Day
watermark 20200423
sdate 20200423
edate 20200428
After Adding Day
watermark 20200424
sdate 20200424
edate 20200428
Before Adding Day
watermark 20200424
sdate 20200424
edate 20200428
After Adding Day
watermark 20200425
sdate 20200425
edate 20200428
Before Adding Day
watermark 20200425
sdate 20200425
edate 20200428
After Adding Day
watermark 20200426
sdate 20200426
edate 20200428
Before Adding Day
watermark 20200426
sdate 20200426
edate 20200428
After Adding Day
watermark 20200427
sdate 20200427
edate 20200428
Before Adding Day
watermark 20200427
sdate 20200427
edate 20200428
After Adding Day
watermark 20200428
sdate 20200428
edate 20200428
Before Adding Day
watermark 20200428
sdate 20200428
edate 20200428
After Adding Day
watermark 20200429
sdate 20200429
edate 20200428
Upvotes: 0
Views: 195
Reputation: 69314
This is because a DateTime object (like all objects in Perl) is a reference.
$ perl -MDateTime -E'say overload::StrVal(DateTime->now)'
DateTime=HASH(0x55dad5805018)
(Note that I've used overload::StrVal()
here as DateTime overloads stringification - which is usually very helpful, but isn't here.)
That large hex number is a memory address. Your variable doesn't actually hold the DateTime data, it holds the address in memory of the DateTime data (which is stored in a hash).
The obvious approach to get a copy of your DateTime object is like this:
my $obj1 = DateTime->now;
my $obj2 = $obj1;
But that's not very useful as $obj1
and $obj2
now both contain the same memory address reference. So if you update one of the references, you'll see the same changes reflected in the other reference.
For this reason, the DateTime class includes a clone()
method. This copies your original object into a new memory location and returns a reference to the new copy. You can then change the new copy without changing the original.
my $obj1 = DateTime->now;
my $obj2 = $obj1->clone;
This program demonstrates the problem (and the solution).
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use DateTime;
say 'standard copy assignment';
my $obj1 = DateTime->now;
my $obj2 = $obj1;
say 'before changing $obj1';
say $obj1;
say $obj2;
$obj1->add(days => 1);
say 'after changing $obj1';
# Both dates are changed
say $obj1;
say $obj2;
say 'using clone()';
$obj1 = DateTime->now;
$obj2 = $obj1->clone;
say 'before changing $obj1';
say $obj1;
say $obj2;
$obj1->add(days => 1);
say 'after changing $obj1';
# Only $obj1 is changed
say $obj1;
say $obj2;
Upvotes: 1