James Finnegan
James Finnegan

Reputation: 235

Using DateTime Object as a separate variable

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

Answers (1)

Dave Cross
Dave Cross

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

Related Questions