The difficulty adjustment algorithm is as follows (from the source code):
unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params)
// Limit adjustment step
int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;
if (nActualTimespan < params.nPowTargetTimespan/4)
nActualTimespan = params.nPowTargetTimespan/4;
if (nActualTimespan > params.nPowTargetTimespan*4)
nActualTimespan = params.nPowTargetTimespan*4;
const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);
bnNew *= nActualTimespan;
bnNew /= params.nPowTargetTimespan;
if (bnNew > bnPowLimit)
bnNew = bnPowLimit;
The adjustment is only run every 2016 block. What it does is it takes the timestamp of the block at the beginning of the last period (2016 blocks prior) and the timestamp of the block that came before the one with the adjusted difficulty. Those timestamps are then used to calculate the amount of time that passed between the first block in the interval and the last block in the interval. Then that timespan is clamped to be at least 1 quarter of the target timespan and at most 4 times the target timespan. To calculate the new difficulty for this interval, that target is multiplied by the actual amount of time that passed, and divided by the targeted amount of time.
Since all nodes are calculating the target at the same block and with the same previous data, the target calculation is deterministic and everyone will land on the same target.