// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import "@openzeppelin/contracts/utils/math/Math.sol";
contract DateOverlapChecker {
uint256 initialOffest;
mapping(uint256 => uint256[]) dateRanges;
uint256 public constant SECONDS_IN_DAY = 86400;
uint256 public constant BUCKET_SIZE = 256;
constructor(uint256 _initialOffest) {
initialOffest = _initialOffest;
}
function checkNoOverlap(
uint256 id,
uint256 startDate,
uint256 endDate
) internal view validDateRange(startDate, endDate) returns (bool) {
uint256[] storage buckets = dateRanges[id];
// There are no any date ranges stored
if (buckets.length == 0) {
return true;
}
uint256 startBucketIdx = dateToBucketIdx(startDate);
// There is no bucket for the given range
if (startBucketIdx >= buckets.length) {
return true;
}
uint256 endBucketIdx = dateToBucketIdx(endDate);
uint256 startDaysWithBucketOffset = dateToBucketRelativeDays(
startDate,
startBucketIdx
);
uint256 endDaysWithBucketOffset = dateToBucketRelativeDays(
endDate,
endBucketIdx
);
// Easy way, date range inside of single bucket
if (startBucketIdx == endBucketIdx) {
return
daysRangeToMask(
startDaysWithBucketOffset,
endDaysWithBucketOffset
) &
buckets[startBucketIdx] ==
0;
}
// Got date range inside of two buckets
uint256 startMask = daysRangeToMask(startDaysWithBucketOffset, 255);
// There is no bucket for the second part
if (endBucketIdx >= buckets.length) {
return startMask & buckets[startBucketIdx] == 0;
}
uint256 endMask = daysRangeToMask(0, endDaysWithBucketOffset);
return
startMask & buckets[startBucketIdx] == 0 &&
endMask & buckets[endBucketIdx] == 0;
}
function freeDateRange(
uint256 id,
uint256 startDate,
uint256 endDate
) internal validDateRange(startDate, endDate) returns (bool) {
uint256[] storage buckets = dateRanges[id];
uint256 startBucketIdx = dateToBucketIdx(startDate);
uint256 endBucketIdx = dateToBucketIdx(endDate);
uint256 startDaysWithBucketOffset = dateToBucketRelativeDays(
startDate,
startBucketIdx
);
uint256 endDaysWithBucketOffset = dateToBucketRelativeDays(
endDate,
endBucketIdx
);
if (
startBucketIdx >= buckets.length || endBucketIdx >= buckets.length
) {
return true;
}
if (startBucketIdx == endBucketIdx) {
buckets[startBucketIdx] &= ~daysRangeToMask(
startDaysWithBucketOffset,
endDaysWithBucketOffset
);
return true;
}
buckets[startBucketIdx] &= ~daysRangeToMask(
startDaysWithBucketOffset,
255
);
buckets[endBucketIdx] &= ~daysRangeToMask(0, endDaysWithBucketOffset);
return true;
}
function allocateDateRange(
uint256 id,
uint256 startDate,
uint256 endDate
) internal validDateRange(startDate, endDate) returns (bool) {
uint256[] storage buckets = dateRanges[id];
uint256 startBucketIdx = dateToBucketIdx(startDate);
uint256 endBucketIdx = dateToBucketIdx(endDate);
uint256 startDaysWithBucketOffset = dateToBucketRelativeDays(
startDate,
startBucketIdx
);
uint256 endDaysWithBucketOffset = dateToBucketRelativeDays(
endDate,
endBucketIdx
);
createBucketsIfNeeded(buckets, startBucketIdx);
if (startBucketIdx == endBucketIdx) {
buckets[startBucketIdx] |= daysRangeToMask(
startDaysWithBucketOffset,
endDaysWithBucketOffset
);
return true;
}
createBucketsIfNeeded(buckets, endBucketIdx);
buckets[startBucketIdx] |= daysRangeToMask(
startDaysWithBucketOffset,
255
);
buckets[endBucketIdx] |= daysRangeToMask(0, endDaysWithBucketOffset);
return true;
}
function dateToBucketIdx(uint256 date) internal view returns (uint256) {
return (date - initialOffest) / SECONDS_IN_DAY / BUCKET_SIZE;
}
function dateToBucketRelativeDays(
uint256 date,
uint256 bucketIdx
) internal view returns (uint256) {
return
(date - initialOffest) / SECONDS_IN_DAY - bucketIdx * BUCKET_SIZE;
}
function daysRangeToMask(
uint256 start,
uint256 end
) internal pure returns (uint256) {
return ((1 << (end - start + 1)) - 1) << start;
}
function createBucketsIfNeeded(
uint256[] storage buckets,
uint256 bucketIdx
) internal {
for (
uint256 idx = Math.max(buckets.length, 1) - 1;
idx <= bucketIdx;
idx++
) {
buckets.push(0);
}
}
modifier validDateRange(uint256 startDate, uint256 endDate) {
require(
startDate >= initialOffest,
"Start date should be after initial offset"
);
require(
endDate >= initialOffest,
"End date should be after initial offset"
);
require(
endDate - startDate < SECONDS_IN_DAY * BUCKET_SIZE,
"Date range cannot exceed 255 days"
);
_;
}
}
Homonyms
cyberia/research/events/backend/ethereum_artifacts/contracts/DateOverlapChecker.sol
DateOverlapChecker.sol