From 2ba13bb7f8f9d4d14bfd7e15ee5dcc09758d378c Mon Sep 17 00:00:00 2001 From: odain Date: Wed, 1 Jul 2026 11:33:33 +0200 Subject: [PATCH] =?UTF-8?q?N=C2=B09746=20-=20handle=20timechange=20during?= =?UTF-8?q?=20synchro=20execution=20+=20cleanup=20m=5FbIsImportPhaseDateKn?= =?UTF-8?q?own?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- synchro/synchrodatasource.class.inc.php | 50 +++++++++++-------- .../synchro/SynchroExecutionTest.php | 44 ++++++++++++++-- 2 files changed, 69 insertions(+), 25 deletions(-) diff --git a/synchro/synchrodatasource.class.inc.php b/synchro/synchrodatasource.class.inc.php index 09a9ada7ef..447495a9e1 100644 --- a/synchro/synchrodatasource.class.inc.php +++ b/synchro/synchrodatasource.class.inc.php @@ -3002,8 +3002,6 @@ class SynchroExecution * */ protected $m_oLastFullLoadStartDate = null; - /** @var bool true if the caller script gave the datetime before import phase was launched */ - protected $m_bIsImportPhaseDateKnown; /** @var \CMDBChange */ protected $m_oChange = null; @@ -3028,10 +3026,7 @@ class SynchroExecution public function __construct($oDataSource, $oImportPhaseStartDate = null) { $this->m_oDataSource = $oDataSource; - - $this->m_bIsImportPhaseDateKnown = ($oImportPhaseStartDate != null); $this->m_oImportPhaseStartDate = $oImportPhaseStartDate; - $this->m_oCtx = new ContextTag(ContextTag::TAG_SYNCHRO); $this->m_oCtx1 = new ContextTag('Synchro:'.$oDataSource->GetRawName()); // More precise context information } @@ -3108,7 +3103,7 @@ class SynchroExecution $this->m_oStatLog->Set('stats_nb_replica_total', $this->m_iCountAllReplicas); $this->m_oStatLog->DBInsert(); - $sLastFullLoad = ($this->m_bIsImportPhaseDateKnown) ? $this->m_oImportPhaseStartDate->format('Y-m-d H:i:s') : 'not specified'; + $sLastFullLoad = (is_null($this->m_oImportPhaseStartDate)) ? 'not specified' : $this->m_oImportPhaseStartDate->format('Y-m-d H:i:s'); $this->m_oStatLog->AddTrace("###### STARTING SYNCHRONIZATION ##### Total: {$this->m_iCountAllReplicas} replica(s). Last full load: '$sLastFullLoad' "); $sSql = 'SELECT NOW();'; $sDBNow = CMDBSource::QueryToScalar($sSql); @@ -3212,20 +3207,18 @@ class SynchroExecution // Compute and keep track of the limit date taken into account for obsoleting replicas // $iFullLoadInterval = $this->m_oDataSource->Get('full_load_periodicity'); // Duration in seconds - if ($this->m_bIsImportPhaseDateKnown) { - $oLimitDate = clone $this->m_oImportPhaseStartDate; - $sInterval = "-$iFullLoadInterval seconds"; - $oLimitDate->Modify($sInterval); - } else { + if (is_null($this->m_oImportPhaseStartDate)) { if ($iFullLoadInterval <= 0) { // we are doing exec phase alone, and the full load interval is set to 0 => we should not update/delete replicas !! // This will prevent actions in DoJob1() method $oLimitDate = new DateTime('1970-01-01'); } else { $oLimitDate = self::GetDataBaseCurrentDateTime(); - $sInterval = "-$iFullLoadInterval seconds"; - $oLimitDate->Modify($sInterval); + $this->ExactlySubtractSeconds($oLimitDate, $iFullLoadInterval); } + } else { + $oLimitDate = clone $this->m_oImportPhaseStartDate; + $this->ExactlySubtractSeconds($oLimitDate, $iFullLoadInterval); } $this->m_oLastFullLoadStartDate = $oLimitDate; if ($bFirstPass) { @@ -3346,10 +3339,10 @@ class SynchroExecution $aArguments['log'] = $this->m_oStatLog->GetKey(); $aArguments['change'] = $this->m_oChange->GetKey(); $aArguments['chunk'] = $iMaxChunkSize; - if ($this->m_bIsImportPhaseDateKnown) { - $aArguments['last_full_load'] = $this->m_oImportPhaseStartDate->Format('Y-m-d H:i:s'); - } else { + if (! is_null($this->m_oImportPhaseStartDate)) { $aArguments['last_full_load'] = ''; + } else { + $aArguments['last_full_load'] = $this->m_oImportPhaseStartDate->Format('Y-m-d H:i:s'); } $this->m_oStatLog->DBUpdate(); @@ -3701,13 +3694,11 @@ class SynchroExecution // Get all the replicas that are to be deleted // - $oDeletionDate = $this->m_oLastFullLoadStartDate; + $oDeletionDate = clone $this->m_oLastFullLoadStartDate; $iDeleteRetention = $this->m_oDataSource->Get('delete_policy_retention'); // Duration in seconds - if ($iDeleteRetention > 0) { - $sInterval = "-$iDeleteRetention seconds"; - $oDeletionDate->Modify($sInterval); - } + $this->ExactlySubtractSeconds($oDeletionDate, $iDeleteRetention); $sDeletionDate = $oDeletionDate->Format('Y-m-d H:i:s'); + if ($bFirstPass) { $this->m_oStatLog->AddTrace("Deletion date: $sDeletionDate"); } @@ -3757,4 +3748,21 @@ class SynchroExecution return false; } + + /** Take into account timechange to apply date difference operation + * @param \DateTime $oDate + * @param $iDurationInSeconds + * @return void + * @throws \Exception + */ + public function ExactlySubtractSeconds(DateTime $oDate, $iDurationInSeconds): void + { + if ($iDurationInSeconds > 0) { + $oDate->setTimezone(new DateTimeZone('UTC')); + $sInterval = "-$iDurationInSeconds seconds"; + $oDate->Modify($sInterval); + $sTimezone = MetaModel::GetConfig()->Get('timezone'); + $oDate->setTimezone(new DateTimeZone($sTimezone)); + } + } } diff --git a/tests/php-unit-tests/unitary-tests/synchro/SynchroExecutionTest.php b/tests/php-unit-tests/unitary-tests/synchro/SynchroExecutionTest.php index 1dcdd1598f..dad8c0b235 100644 --- a/tests/php-unit-tests/unitary-tests/synchro/SynchroExecutionTest.php +++ b/tests/php-unit-tests/unitary-tests/synchro/SynchroExecutionTest.php @@ -1,10 +1,46 @@ Set('timezone', $sTZ); + return $sTZ; + } + public function testGetDateMinusRetentionWithTimeChange() + { + $sTZ = $this->InitAndGetTZ(); + $oObj = new SynchroExecution($this->createMock(SynchroDataSource::class)); + $oDate = DateTime::createFromFormat('Y-m-d H:i:s', "2026-03-29 03:30:01", new DateTimeZone($sTZ)); + + $oObj->ExactlySubtractSeconds($oDate, 3600); + //03h30 minus 1hours => 03h minus 30mn => 03h + // => 03h with timechange => 2h + // => 02 minus 30mn => 01h30 + $this->assertEquals("2026-03-29 01:30:01", $oDate->Format('Y-m-d H:i:s')); + } + + public function testGetDateMinusRetentionWithTimeChangeAndUTC() + { + $sTZ = $this->InitAndGetTZ('UTC'); + $oObj = new SynchroExecution($this->createMock(SynchroDataSource::class)); + $oDate = DateTime::createFromFormat('Y-m-d H:i:s', "2026-03-29 03:30:01", new DateTimeZone($sTZ)); + + $oObj->ExactlySubtractSeconds($oDate, 3600); + $this->assertEquals("2026-03-29 02:30:01", $oDate->Format('Y-m-d H:i:s')); + } + + public function testGetDateMinusRetention() + { + $sTZ = $this->InitAndGetTZ(); + $oObj = new SynchroExecution($this->createMock(SynchroDataSource::class)); + $oDate = DateTime::createFromFormat('Y-m-d H:i:s', "2026-04-01 03:30:01", new DateTimeZone($sTZ)); + + $oObj->ExactlySubtractSeconds($oDate, 3600); + $this->assertEquals("2026-04-01 02:30:01", $oDate->Format('Y-m-d H:i:s')); + } } -} \ No newline at end of file