├── .gas-snapshot ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── foundry.toml ├── package.json ├── src └── DateTimeLib.sol └── test ├── DateTimeLib.t.sol └── utils └── TestPlus.sol /.gas-snapshot: -------------------------------------------------------------------------------- 1 | DateTimeLibTest:testDateTimeArithmeticReverts() (gas: 3864) 2 | DateTimeLibTest:testDateTimeMaxSupported() (gas: 2427) 3 | DateTimeLibTest:testDateToEpochDay() (gas: 1559) 4 | DateTimeLibTest:testDateToEpochDayDifferential((uint256,uint256,uint256,uint256,uint256,uint256)) (runs: 256, μ: 2070, ~: 2046) 5 | DateTimeLibTest:testDateToEpochDayDifferential2((uint256,uint256,uint256,uint256,uint256,uint256)) (runs: 256, μ: 1971, ~: 1944) 6 | DateTimeLibTest:testDaysInMonth() (gas: 1224) 7 | DateTimeLibTest:testDaysToDate() (gas: 1958) 8 | DateTimeLibTest:testEpochDayToDateDifferential(uint256) (runs: 256, μ: 1716, ~: 1663) 9 | DateTimeLibTest:testEpochDayToDateDifferential2(uint256) (runs: 256, μ: 1677, ~: 1608) 10 | DateTimeLibTest:testFuzzAddSubDiffDays(uint256,uint256) (runs: 256, μ: 3740, ~: 3768) 11 | DateTimeLibTest:testFuzzAddSubDiffHours(uint256,uint256) (runs: 256, μ: 3674, ~: 3701) 12 | DateTimeLibTest:testFuzzAddSubDiffMinutes(uint256,uint256) (runs: 256, μ: 3649, ~: 3654) 13 | DateTimeLibTest:testFuzzAddSubDiffMonths(uint256,uint256) (runs: 256, μ: 6069, ~: 6103) 14 | DateTimeLibTest:testFuzzAddSubDiffSeconds(uint256,uint256) (runs: 256, μ: 3488, ~: 3466) 15 | DateTimeLibTest:testFuzzAddSubDiffYears(uint256,uint256) (runs: 256, μ: 5908, ~: 5944) 16 | DateTimeLibTest:testFuzzDateTimeToAndFroTimestamp((uint256,uint256,uint256,uint256,uint256,uint256)) (runs: 256, μ: 3659, ~: 3696) 17 | DateTimeLibTest:testFuzzDateToAndFroEpochDay((uint256,uint256,uint256,uint256,uint256,uint256)) (runs: 256, μ: 2350, ~: 2323) 18 | DateTimeLibTest:testFuzzDateToAndFroEpochDay() (gas: 479867) 19 | DateTimeLibTest:testFuzzDateToAndFroTimestamp() (gas: 517521) 20 | DateTimeLibTest:testFuzzDateToEpochDayGas() (gas: 369353) 21 | DateTimeLibTest:testFuzzDateToEpochDayGas2() (gas: 374963) 22 | DateTimeLibTest:testFuzzDayOfWeek() (gas: 175297) 23 | DateTimeLibTest:testFuzzDaysInMonth(uint256,uint256) (runs: 256, μ: 1001, ~: 1021) 24 | DateTimeLibTest:testFuzzEpochDayToDate(uint256) (runs: 256, μ: 1018, ~: 1018) 25 | DateTimeLibTest:testFuzzEpochDayToDateGas() (gas: 197193) 26 | DateTimeLibTest:testFuzzEpochDayToDateGas2() (gas: 215623) 27 | DateTimeLibTest:testFuzzIsLeapYear(uint256) (runs: 256, μ: 503, ~: 472) 28 | DateTimeLibTest:testFuzzIsSupportedDateTime((uint256,uint256,uint256,uint256,uint256,uint256)) (runs: 256, μ: 2540, ~: 2504) 29 | DateTimeLibTest:testFuzzIsWeekEnd(uint256) (runs: 256, μ: 674, ~: 607) 30 | DateTimeLibTest:testFuzzMondayTimestamp(uint256) (runs: 256, μ: 731, ~: 651) 31 | DateTimeLibTest:testFuzzNthWeekdayInMonthOfYearTimestamp(uint256,uint256,uint256,uint256) (runs: 256, μ: 3198, ~: 3236) 32 | DateTimeLibTest:testIsLeapYear() (gas: 741) 33 | DateTimeLibTest:testIsSupportedDateFalse() (gas: 1696) 34 | DateTimeLibTest:testIsSupportedDateTrue() (gas: 670) 35 | DateTimeLibTest:testIsSupportedEpochDayFalse() (gas: 597) 36 | DateTimeLibTest:testIsSupportedEpochDayTrue() (gas: 328) 37 | DateTimeLibTest:testIsSupportedTimestampFalse() (gas: 576) 38 | DateTimeLibTest:testIsSupportedTimestampTrue() (gas: 326) 39 | DateTimeLibTest:testMondayTimestamp() (gas: 1128) 40 | DateTimeLibTest:testNthWeekdayInMonthOfYearTimestamp() (gas: 12031) 41 | DateTimeLibTest:testWeekday() (gas: 725) 42 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push] 4 | 5 | jobs: 6 | tests: 7 | name: Forge Testing 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | profile: [via-ir,min-solc,min-solc-via-ir,intense-0,intense-1] 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Install Foundry 18 | uses: onbjerg/foundry-toolchain@v1 19 | with: 20 | version: nightly 21 | 22 | - name: Install Dependencies 23 | run: forge install 24 | 25 | - name: Run Tests with ${{ matrix.profile }} 26 | run: FOUNDRY_PROFILE=${{ matrix.profile }} forge test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # NodeJS files 2 | node_modules/ 3 | coverage 4 | coverage.json 5 | typechain 6 | package-lock.json 7 | 8 | # Hardhat files 9 | cache 10 | artifacts 11 | 12 | cache/ 13 | out/ 14 | 15 | # Ignore Environment Variables! 16 | .env 17 | .env.prod 18 | 19 | # Ignore all vscode settings 20 | .vscode/ 21 | 22 | # Ignore flattened files 23 | flattened.txt 24 | 25 | broadcast 26 | 27 | # Coverage 28 | lcov.info 29 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | lib -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "printWidth": 100, 4 | 5 | "overrides": [ 6 | { 7 | "files": "*.sol", 8 | "options": { 9 | "tabWidth": 4, 10 | "printWidth": 120 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Akshay Tarpara 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DateTimeLib 2 | 3 | [![NPM][npm-shield]][npm-url] 4 | [![CI][ci-shield]][ci-url] 5 | [![MIT License][license-shield]][license-url] 6 | 7 | Gas-Efficient Solidity DateTime Library 8 | 9 | ## Safety 10 | 11 | This is **experimental software** and is provided on an "as is" and "as available" basis. 12 | 13 | We **do not give any warranties** and **will not be liable for any loss** incurred through any use of this codebase. 14 | 15 | ## Installation 16 | 17 | To install with [**Foundry**](https://github.com/gakonst/foundry): 18 | 19 | ```sh 20 | forge install Atarpara/DateTimeLib 21 | ``` 22 | 23 | To install with [**Hardhat**](https://github.com/nomiclabs/hardhat) or [**Truffle**](https://github.com/trufflesuite/truffle): 24 | 25 | ```sh 26 | npm install 27 | ``` 28 | 29 | ## Conventions 30 | 31 | All dates, times and Unix timestamps are [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). 32 | 33 | Unit | Range | Notes | 34 | :------------- |:---------------------------: |:------------------ | 35 | timestamp | 0..0x1e18549868c76ff | Unix timestamp. | 36 | epochDay | 0..0x16d3e098039 | Days since 1970-01-01. | 37 | year | 1970..0xffffffff | Gregorian calendar year. | 38 | month | 1..12 | Gregorian calendar month. | 39 | day | 1..31 | Gregorian calendar day of month. | 40 | weekday | 1..7 | The day of the week (1-indexed). | 41 | 42 | All functions operate on the `uint256` timestamp data type. 43 | 44 |
45 | 46 |
47 | 48 | ## Functions 49 | 50 | ### dateToEpochDay 51 | Calculate the number of days `days` from 1970/01/01 to `year`/`month`/`day`. 52 | 53 | ```javascript 54 | function dateToEpochDay(uint256 year, uint256 month, uint256 day) internal pure returns (uint256 epochDay) 55 | ``` 56 | 57 | **NOTE** This function does not validate the `year`/`month`/`day` input. Use [`isSupportedDate(..)`](#isSupportedDate) to validate the input if necessary. 58 | 59 | Example: 60 | 61 | ```javascript 62 | if(DateTimeLib.isSupportedDate(1970,2,1)) 63 | uint256 day = DateTimeLib.dateToEpochDay(1970,2,1) // returns 31 day 64 | if(DateTimeLib.isSupportedDate(1971,1,1)) 65 | uint256 day = DateTimeLib.dateToEpochDay(1971,1,1) // returns 365 day 66 | ``` 67 |
68 | 69 | ### epochDayToDate 70 | 71 | Calculate `year`/`month`/`day` from the number of days `days` since 1970/01/01 . 72 | 73 | ```javascript 74 | function epochDayToDate(uint256 epochDay) internal pure returns (uint256 year, uint256 month, uint256 day) 75 | ``` 76 | **NOTE** This function does not validate the `epochDay` input. Use [`isSupportedEpochDay(..)`](#isSupportedEpochDay) to validate the input if necessary. 77 | 78 | Example: 79 | 80 | ```javascript 81 | if(DateTimeLib.isSupportedEpochDay(0)) 82 | (uint256 year, uint256 month, uint256 day) = DateTimeLib.dateToEpochDay(0) // 1970-01-01 83 | if(DateTimeLib.isSupportedEpochDay(224)) 84 | (uint256 year, uint256 month, uint256 day) = DateTimeLib.dateToEpochDay(224) // 1970-08-13 85 | ``` 86 |
87 | 88 | ### dateToTimestamp 89 | 90 | Calculate the `timestamp` from `year`/`month`/`day`. 91 | 92 | ```javascript 93 | function dateToTimestamp(uint256 year, uint256 month, uint256 day) internal pure returns (uint256 result) 94 | ``` 95 | 96 | **NOTE** This function does not validate the `year`/`month`/`day` input. Use [`isSupportedDate(...)`](#isSupportedDate) to validate the input if necessary. 97 | 98 | Example: 99 | 100 | ```javascript 101 | if(DateTimeLib.isSupportedDate(1970,2,1)) 102 | uint256 timestamp = DateTimeLib.dateToTimestamp(1970,2,1) // returns 2658600 103 | if(DateTimeLib.isSupportedDate(2022,11,10)) 104 | uint256 timestamp = DateTimeLib.dateToTimestamp(2022,11,10) // returns 1668018600 105 | ``` 106 | 107 |
108 | 109 | ### timestampToDate 110 | 111 | Calculate `year`/`month`/`day` from `timestamp`. 112 | 113 | ```javascript 114 | function timestampToDate(uint256 timestamp) internal pure returns (uint256 year, uint256 month, uint256 day) 115 | ``` 116 | **NOTE** This function does not validate the `timestamp` input. Use [`isSupportedTimestamp(...)`](#isSupportedTimestamp) to validate the input if necessary. 117 | 118 | Example: 119 | 120 | ```javascript 121 | if(DateTimeLib.isSupportedTimestamp(2658650)) 122 | (uint256 year, uint256 month, uint256 day) = DateTimeLib.timestampToDate(2658600) // returns 1970-02-01 123 | if(DateTimeLib.isSupportedTimestamp(1668018900)) 124 | (uint256 year, uint256 month, uint256 day) = DateTimeLib.timestampToDate(1668018900) // returns 2022-11-10 125 | ``` 126 | 127 |
128 | 129 | ### dateTimeToTimestamp 130 | 131 | Calculate `timestamp` from `year`/`month`/`day`/`hour`/`minute`/`second`. 132 | 133 | ```javascript 134 | function dateTimeToTimestamp(uint256 year, uint256 month, uint256 day, uint256 hour, uint256 minute, uint256 second) internal pure returns (uint256 result) 135 | ``` 136 | **NOTE** This function does not validate the `year`/`month`/`day`/`hour`/`minute`/`second` input. Use [`isSupportedDateTime(...)`](#isSupportedDateTime) to validate the input if necessary. 137 | 138 | Example: 139 | 140 | ```javascript 141 | if(DateTimeLib.isSupportedDateTime(1970,02,01,23,30,59)) 142 | uint256 timestamp = DateTimeLib.dateTimeToTimestamp(1970,02,01,23,30,59) // returns 2763059 143 | if(DateTimeLib.isSupportedDateTime(2022,11,10,06,47,30)) 144 | uint256 timestamp = DateTimeLib.dateTimeToTimestamp(2022,11,10,12,17,30) // returns 1668062850 145 | ``` 146 | 147 |
148 | 149 | ### timestampToDateTime 150 | 151 | Calculate `year`/`month`/`day`/`hour`/`minute`/`second` from `timestamp`. 152 | 153 | ```javascript 154 | function timestampToDateTime(uint256 timestamp) internal pure returns (uint256 year, uint256 month, uint256 day, uint256 hour, uint256 minute, uint256 second) 155 | ``` 156 | **NOTE** This function does not validate the `year`/`month`/`day`/`hour`/`minute`/`second` input. Use [`isSupportedTimestamp(...)`](#isSupportedTimestamp) to validate the input if necessary. 157 | 158 | Example: 159 | 160 | ```javascript 161 | if(DateTimeLib.isSupportedTimestamp(2763059)) 162 | (uint256 year, uint256 month, uint256 day, uint256 hour, uint256 minute, uint256 second) = DateTimeLib.timestampToDateTime(2763059) // returns 1970-02-01 23:30:59 163 | if(DateTimeLib.isSupportedTimestamp(1668062850)) 164 | (uint256 year, uint256 month, uint256 day, uint256 hour, uint256 minute, uint256 second) = DateTimeLib.timestampToDateTime(1668062850) // returns 2022-11-10 12:17:30 165 | ``` 166 | 167 |
168 | 169 | ### isLeapYear 170 | 171 | Check given year is leap or not. 172 | 173 | ```javascript 174 | function isLeapYear(uint256 year) internal pure returns (bool leap) 175 | ``` 176 | Example: 177 | 178 | ```javascript 179 | bool isLeap = DateTimeLib.isLeapYear(1900) // returns False 180 | bool isLeap = DateTimeLib.isLeapYear(2004) // returns True 181 | bool isLeap = DateTimeLib.isLeapYear(2400) // returns True 182 | ``` 183 | 184 |
185 | 186 | ### daysInMonth 187 | 188 | Return number of day in the month `daysInMonth` for the month specified by `year`/`month`. 189 | 190 | ```javascript 191 | function daysInMonth(uint256 year, uint256 month) internal pure returns (uint256 result) 192 | ``` 193 | Example: 194 | 195 | ```javascript 196 | uint256 day = DateTimeLib.daysInMonth(01, 1900) // returns 31 197 | uint256 day = DateTimeLib.daysInMonth(02, 2001) // returns 28 198 | uint256 day = DateTimeLib.daysInMonth(02, 2004) // returns 29 199 | ``` 200 | 201 |
202 | 203 | ### weekday 204 | 205 | Return the day of the week `weekday` (1 = Monday,2 = Tuesday ..., 7 = Sunday) for the date specified by `timestamp`. 206 | 207 | ```javascript 208 | function weekday(uint256 timestamp) internal pure returns (uint256 result) 209 | ``` 210 |
211 | 212 | ### isSupportedDate 213 | 214 | Check the given date is valid or not. 215 | ```javascript 216 | function isSupportedDate(uint256 year, uint256 month, uint256 day) internal pure returns (bool result) 217 | ``` 218 | **NOTE** This algorithm supported fully uint256 limit but Restricted max supported year to type(uint32).max. By the time, we will already be either extinct, an interstellar species, or the Earth's motion would have drastically changed. A smaller supported range will mean smaller bytecode needed to validate the dates. 219 | 220 |
221 | 222 | ### isSupportedDateTime 223 | 224 | Check the given datetime is valid or not. 225 | ```javascript 226 | function function isSupportedDateTime(uint256 year, uint256 month, uint256 day, uint256 hour, uint256 minute, uint256 second) internal pure returns (bool result) 227 | ``` 228 | 229 |
230 | 231 | ### isSupportedEpochDay 232 | 233 | Check the given epoch day is valid or not. 234 | ```javascript 235 | function isSupportedEpochDay(uint256 epochDay) internal pure returns (bool result) 236 | ``` 237 | **NOTE** This algorithm supported fully uint256 limit but Restricted max supported epochDay to `MAX_SUPPORTED_EPOCH_DAY`. By the time, we will already be either extinct, an interstellar species, or the Earth's motion would have drastically changed. 238 | 239 |
240 | 241 | ### isSupportedTimestamp 242 | 243 | Check the given timestamp is valid or not. 244 | ```javascript 245 | function isSupportedTimestamp(uint256 timestamp) internal pure returns (bool result) 246 | ``` 247 | **NOTE** This algorithm supported fully uint256 limit but Restricted max supported timstamp to `MAX_SUPPORTED_TIMESTAMP`. 248 | 249 |
250 | 251 | ### nthWeekdayInMonthOfYearTimestamp 252 | 253 | Return `timestamp` of the Nth weekday from the `year`/`month`. 254 | 255 | ```javascript 256 | function nthWeekdayInMonthOfYearTimestamp(uint256 year, uint256 month, uint256 n, uint256 wd) internal pure returns (uint256 result) 257 | ``` 258 | **NOTE** This function does not validate the `year`/`month` and `wd` input. Use [`isSupportedDate(year,month,1)`](#isSupportedDate) to validate for `year`/`month` and `wd` must be in range of [1,7]. 259 | 260 | Example: 261 | 262 | ```javascript 263 | uint256 timestamp = DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022,12,1,DateTimeLib.FRI) // returns 1669939200 (1st Friday December 2022) 264 | uint256 timestamp = DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022,11,6,DateTimeLib.WED) // returns 0 (6th Wednesday November 2022) 265 | ``` 266 | 267 | 268 |
269 | 270 | ### mondayTimestamp 271 | Calculate `timestamp` of the most recent Monday. 272 | 273 | ```javascript 274 | function mondayTimestamp(uint256 timestamp) internal pure returns (uint256 result) 275 | ``` 276 | **NOTE** If timestamp < 345600 it returns 0 as per UTC it is thursday. 277 | 278 |
279 | 280 | ### isWeekEnd 281 | Calculate `timestamp` falls on a Saturday or Sunday 282 | 283 | ```javascript 284 | function isWeekEnd(uint256 timestamp) internal pure returns (bool result) 285 | ``` 286 | 287 |
288 | 289 | ### addYears 290 | 291 | Add `numYears` years to the date and time specified by timestamp. 292 | 293 | Note that the resulting day of the month will be adjusted if it exceeds the valid number of days in the month. For example, if the original date is 2020/02/29 and an additional year is added to this date, the resulting date will be an invalid date of 2021/02/29. The resulting date is then adjusted to 2021/02/28. 294 | 295 | ```javascript 296 | function addYears(uint256 timestamp, uint256 numYears) internal pure returns (uint256 result) 297 | ``` 298 |
299 | 300 | ### addMonths 301 | 302 | Add `numMonths` months to the date and time specified by timestamp. 303 | 304 | Note that the resulting day of the month will be adjusted if it exceeds the valid number of days in the month. For example, if the original date is 2019/01/31 and an additional month is added to this date, the resulting date will be an invalid date of 2019/02/31. The resulting date is then adjusted to 2019/02/28. 305 | 306 | ```javascript 307 | function addMonths(uint256 timestamp, uint256 numMonths) internal pure returns (uint256 result) 308 | ``` 309 | 310 |
311 | 312 | ### addDays 313 | 314 | Add `numDays` days to the date and time specified by timestamp. 315 | 316 | ```javascript 317 | function addDays(uint256 timestamp, uint256 numDays) internal pure returns (uint256 result) 318 | ``` 319 | 320 | 321 | ### addHours 322 | 323 | Add `numHours` hours to the date and time specified by timestamp. 324 | 325 | ```javascript 326 | function addHours(uint256 timestamp, uint256 numHours) internal pure returns (uint256 result) 327 | ``` 328 | 329 |
330 | 331 | ### addMinutes 332 | 333 | Add `numMinutes` minutes to the date and time specified by timestamp. 334 | 335 | ```javascript 336 | function addMinutes(uint256 timestamp, uint256 numMinutes) internal pure returns (uint256 result) 337 | ``` 338 | 339 |
340 | 341 | ### addSeconds 342 | 343 | Add `numSeconds` seconds to the date and time specified by timestamp. 344 | 345 | ```javascript 346 | function addSeconds(uint256 timestamp, uint256 numSeconds) internal pure returns (uint256 result) 347 | ``` 348 | 349 |
350 | 351 | ### subYears 352 | 353 | Subtracts `numYears` years from the unix timestamp. 354 | 355 | Note that the resulting day of the month will be adjusted if it exceeds the valid number of days in the month. For example, if the original date is 2020/02/29 and a year is subtracted from this date, the resulting date will be an invalid date of 2019/02/29. The resulting date is then adjusted to 2019/02/28. 356 | 357 | ```javascript 358 | function subYears(uint256 timestamp, uint256 numYears) internal pure returns (uint256 result) 359 | ``` 360 | 361 |
362 | 363 | ### subMonths 364 | 365 | Subtracts `numMonths` months from the unix timestamp. 366 | 367 | Note that the resulting day of the month will be adjusted if it exceeds the valid number of days in the month. For example, if the original date is 2019/03/31 and a month is subtracted from this date, the resulting date will be an invalid date of 2019/02/31. The resulting date is then adjusted to 2019/02/28. 368 | 369 | ```javascript 370 | function subMonths(uint256 timestamp, uint256 numMonths) internal pure returns (uint256 result) 371 | ``` 372 | 373 |
374 | 375 | ### subDays 376 | 377 | Subtracts `numDays` days from the unix timestamp. 378 | ```javascript 379 | function subDays(uint256 timestamp, uint256 numDays) internal pure returns (uint256 result) 380 | ``` 381 | 382 |
383 | 384 | ### subHours 385 | 386 | Subtracts `numHours` from the unix timestamp. 387 | 388 | ```javascript 389 | function subHours(uint256 timestamp, uint256 numHours) internal pure returns (uint256 result) 390 | ``` 391 | 392 |
393 | 394 | ### subMinutes 395 | 396 | Subtracts `numMinutes` from the unix timestamp. 397 | 398 | ```javascript 399 | function subMinutes(uint256 timestamp, uint256 numMinutes) internal pure returns (uint256 result) 400 | ``` 401 | 402 |
403 | 404 | ### subSeconds 405 | 406 | Subtracts `numSeconds` from the unix timestamp. 407 | 408 | ```javascript 409 | function subSeconds(uint256 timestamp, uint256 numSeconds) internal pure returns (uint256 result) 410 | ``` 411 | 412 |
413 | 414 | ### diffYears 415 | 416 | Calculate the number of years between the dates specified by `fromTimeStamp` and `toTimestamp`. 417 | 418 | Note that Even if the true time difference is less than a year, the difference can be non-zero is the timestamps are from diffrent Gregorian calendar years. 419 | ```javascript 420 | function diffYears(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 result) 421 | ``` 422 | 423 |
424 | 425 | ### diffMonths 426 | 427 | Calculate the number of months between the dates specified by `fromTimeStamp` and `toTimestamp`. 428 | 429 | Note that Even if the true time difference is less than a month, the difference can be non-zero is the timestamps are from diffrent Gregorian calendar months. 430 | 431 | ```javascript 432 | function diffMonths(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 result) 433 | ``` 434 | 435 |
436 | 437 | ### diffDays 438 | 439 | Calculate the number of days between the dates specified by `fromTimeStamp` and `toTimestamp`. 440 | ```javascript 441 | function diffDays(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 result) 442 | ``` 443 | 444 |
445 | 446 | ### diffHours 447 | 448 | Calculate the number of hours between the dates specified by `fromTimeStamp` and `toTimestamp`. 449 | 450 | ```javascript 451 | function diffHours(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 result) 452 | ``` 453 | 454 |
455 | 456 | ### diffMinutes 457 | 458 | Calculate the number of minutes between the dates specified by `fromTimeStamp` and `toTimestamp`. 459 | 460 | ```javascript 461 | function diffMinutes(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 result) 462 | ``` 463 | 464 |
465 | 466 | ### diffSeconds 467 | 468 | Calculate the number of seconds between the dates specified by `fromTimeStamp` and `toTimestamp`. 469 | 470 | ```javascript 471 | function diffSeconds(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 result) 472 | ``` 473 | 474 |
475 | 476 | ## Acknowledgements 477 | 478 | This repository is inspired by or directly modified from many sources, primarily: 479 | - [solady](https://github.com/Vectorized/solady) 480 | - [BokkyPooBahsDateTimeLibrary](https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary) 481 | 482 | 483 | ## References 484 | 485 | A copy of the webpage with the algorithm [Date Time Algorithm](https://howardhinnant.github.io/date_algorithms.html). 486 | 487 | [npm-shield]: https://img.shields.io/npm/v/@atarpara/datetimelib.svg 488 | [npm-url]: https://www.npmjs.com/package/@atarpara/datetimelib 489 | 490 | [ci-shield]: https://img.shields.io/github/workflow/status/atarpara/datetimelib/ci?label=build 491 | [ci-url]: https://github.com/atarpara/datetimelib/actions/workflows/ci.yml 492 | 493 | [license-shield]: https://img.shields.io/badge/License-MIT-green.svg 494 | [license-url]: https://github.com/vectorized/solady/blob/main/LICENSE.txt 495 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | optimizer = true 6 | optimizer_runs = 1_000 7 | 8 | [fuzz] 9 | runs = 256 10 | max_test_rejects = 65536 11 | seed = '0x3e8' 12 | dictionary_weight = 40 13 | include_storage = true 14 | include_push_bytes = true 15 | 16 | 17 | 18 | 19 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solc-datetimelib", 3 | "license": "MIT", 4 | "version": "1.0.0", 5 | "description": "Optimized Solidity library for date time.", 6 | "files": [ 7 | "src/**/*.sol" 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Atarpara/DateTimeLib.git" 12 | }, 13 | "dependencies": { 14 | "prettier": "^2.7.1", 15 | "prettier-plugin-solidity": "^1.0.0-rc.1" 16 | }, 17 | "scripts": { 18 | "lint": "prettier --write **.sol" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/Atarpara/DateTimeLib/issues" 22 | }, 23 | "homepage": "https://github.com/Atarpara/DateTimeLib#readme", 24 | "author": "atarpara" 25 | } 26 | -------------------------------------------------------------------------------- /src/DateTimeLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /// @notice Library for date time operations. 5 | /// @author Atarpara (https://github.com/Atarpara/DateTimeLib/blob/master/src/DateTimeLib.sol) 6 | /// Conventions: 7 | /// --------------------------------------------------------------------+ 8 | /// Unit | Range | Notes | 9 | /// --------------------------------------------------------------------| 10 | /// timestamp | 0..0x1e18549868c76ff | Unix timestamp. | 11 | /// epochDay | 0..0x16d3e098039 | Days since 1970-01-01. | 12 | /// year | 1970..0xffffffff | Gregorian calendar year. | 13 | /// month | 1..12 | Gregorian calendar month. | 14 | /// day | 1..31 | Gregorian calendar day of month. | 15 | /// weekday | 1..7 | The day of the week (1-indexed). | 16 | /// --------------------------------------------------------------------+ 17 | /// All timestamps of days are rounded down to 00:00:00 UTC. 18 | library DateTimeLib { 19 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ 20 | /* CUSTOM ERRORS */ 21 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ 22 | 23 | /// @dev The date time addition has overflowed. 24 | error Overflow(); 25 | 26 | /// @dev The date time subtraction has underflowed. 27 | error Underflow(); 28 | 29 | /// @dev The `fromTimestamp` is greater than the `toTimestamp`. 30 | error InvalidDiff(); 31 | 32 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ 33 | /* CONSTANTS */ 34 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ 35 | 36 | // Weekdays are 1-indexed for a traditional rustic feel. 37 | 38 | // "And on the seventh day God finished his work that he had done, 39 | // and he rested on the seventh day from all his work that he had done." 40 | // -- Genesis 2:2 41 | 42 | uint256 internal constant MON = 1; 43 | uint256 internal constant TUE = 2; 44 | uint256 internal constant WED = 3; 45 | uint256 internal constant THU = 4; 46 | uint256 internal constant FRI = 5; 47 | uint256 internal constant SAT = 6; 48 | uint256 internal constant SUN = 7; 49 | 50 | // Months and days of months are 1-indexed for ease of use. 51 | 52 | uint256 internal constant JAN = 1; 53 | uint256 internal constant FEB = 2; 54 | uint256 internal constant MAR = 3; 55 | uint256 internal constant APR = 4; 56 | uint256 internal constant MAY = 5; 57 | uint256 internal constant JUN = 6; 58 | uint256 internal constant JUL = 7; 59 | uint256 internal constant AUG = 8; 60 | uint256 internal constant SEP = 9; 61 | uint256 internal constant OCT = 10; 62 | uint256 internal constant NOV = 11; 63 | uint256 internal constant DEC = 12; 64 | 65 | // These limits are large enough for most practical purposes. 66 | // Inputs that exceed these limits result in undefined behavior. 67 | 68 | uint256 internal constant MAX_SUPPORTED_YEAR = 0xffffffff; 69 | uint256 internal constant MAX_SUPPORTED_EPOCH_DAY = 0x16d3e098039; 70 | uint256 internal constant MAX_SUPPORTED_TIMESTAMP = 0x1e18549868c76ff; 71 | 72 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ 73 | /* DATE TIME OPERATIONS */ 74 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ 75 | 76 | /// @dev Returns the number of days since 1970-01-01 from (`year`,`month`,`day`). 77 | /// See: https://howardhinnant.github.io/date_algorithms.html 78 | /// Note: Inputs outside the supported ranges result in undefined behavior. 79 | /// Use {isSupportedDate} to check if the inputs are supported. 80 | function dateToEpochDay( 81 | uint256 year, 82 | uint256 month, 83 | uint256 day 84 | ) internal pure returns (uint256 epochDay) { 85 | /// @solidity memory-safe-assembly 86 | assembly { 87 | year := sub(year, lt(month, 3)) 88 | let doy := add(shr(11, add(mul(62719, mod(add(month, 9), 12)), 769)), day) 89 | let yoe := mod(year, 400) 90 | let doe := sub(add(add(mul(yoe, 365), shr(2, yoe)), doy), div(yoe, 100)) 91 | epochDay := sub(add(mul(div(year, 400), 146097), doe), 719469) 92 | } 93 | } 94 | 95 | /// @dev Returns (`year`,`month`,`day`) from the number of days since 1970-01-01. 96 | /// Note: Inputs outside the supported ranges result in undefined behavior. 97 | /// Use {isSupportedDays} to check if the inputs is supported. 98 | function epochDayToDate(uint256 epochDay) 99 | internal 100 | pure 101 | returns ( 102 | uint256 year, 103 | uint256 month, 104 | uint256 day 105 | ) 106 | { 107 | /// @solidity memory-safe-assembly 108 | assembly { 109 | epochDay := add(epochDay, 719468) 110 | let doe := mod(epochDay, 146097) 111 | let yoe := div(sub(sub(add(doe, div(doe, 36524)), div(doe, 1460)), eq(doe, 146096)), 365) 112 | let doy := sub(doe, sub(add(mul(365, yoe), shr(2, yoe)), div(yoe, 100))) 113 | let mp := div(add(mul(5, doy), 2), 153) 114 | day := add(sub(doy, shr(11, add(mul(mp, 62719), 769))), 1) 115 | month := sub(add(mp, 3), mul(gt(mp, 9), 12)) 116 | year := add(add(yoe, mul(div(epochDay, 146097), 400)), lt(month, 3)) 117 | } 118 | } 119 | 120 | /// @dev Returns the unix timestamp from (`year`,`month`,`day`). 121 | /// Note: Inputs outside the supported ranges result in undefined behavior. 122 | /// Use {isSupportedDate} to check if the inputs are supported. 123 | function dateToTimestamp( 124 | uint256 year, 125 | uint256 month, 126 | uint256 day 127 | ) internal pure returns (uint256 result) { 128 | unchecked { 129 | result = dateToEpochDay(year, month, day) * 86400; 130 | } 131 | } 132 | 133 | /// @dev Returns (`year`,`month`,`day`) from the given unix timestamp. 134 | /// Note: Inputs outside the supported ranges result in undefined behavior. 135 | /// Use {isSupportedTimestamp} to check if the inputs are supported. 136 | function timestampToDate(uint256 timestamp) 137 | internal 138 | pure 139 | returns ( 140 | uint256 year, 141 | uint256 month, 142 | uint256 day 143 | ) 144 | { 145 | unchecked { 146 | (year, month, day) = epochDayToDate(timestamp / 86400); 147 | } 148 | } 149 | 150 | /// @dev Returns the unix timestamp from 151 | /// (`year`,`month`,`day`,`hour`,`minute`,`second`). 152 | /// Note: Inputs outside the supported ranges result in undefined behavior. 153 | /// Use {isSupportedDateTime} to check if the inputs are supported. 154 | function dateTimeToTimestamp( 155 | uint256 year, 156 | uint256 month, 157 | uint256 day, 158 | uint256 hour, 159 | uint256 minute, 160 | uint256 second 161 | ) internal pure returns (uint256 result) { 162 | unchecked { 163 | result = dateToEpochDay(year, month, day) * 86400 + hour * 3600 + minute * 60 + second; 164 | } 165 | } 166 | 167 | /// @dev Returns (`year`,`month`,`day`,`hour`,`minute`,`second`) 168 | /// from the given unix timestamp. 169 | /// Note: Inputs outside the supported ranges result in undefined behavior. 170 | /// Use {isSupportedTimestamp} to check if the inputs are supported. 171 | function timestampToDateTime(uint256 timestamp) 172 | internal 173 | pure 174 | returns ( 175 | uint256 year, 176 | uint256 month, 177 | uint256 day, 178 | uint256 hour, 179 | uint256 minute, 180 | uint256 second 181 | ) 182 | { 183 | unchecked { 184 | (year, month, day) = epochDayToDate(timestamp / 86400); 185 | uint256 secs = timestamp % 86400; 186 | hour = secs / 3600; 187 | secs = secs % 3600; 188 | minute = secs / 60; 189 | second = secs % 60; 190 | } 191 | } 192 | 193 | /// @dev Returns if the `year` is leap. 194 | function isLeapYear(uint256 year) internal pure returns (bool leap) { 195 | /// @solidity memory-safe-assembly 196 | assembly { 197 | leap := iszero(and(add(mul(iszero(mod(year, 25)), 12), 3), year)) 198 | } 199 | } 200 | 201 | /// @dev Returns number of days in given `month` of `year`. 202 | function daysInMonth(uint256 year, uint256 month) internal pure returns (uint256 result) { 203 | bool flag = isLeapYear(year); 204 | /// @solidity memory-safe-assembly 205 | assembly { 206 | // `daysInMonths = [31,28,31,30,31,30,31,31,30,31,30,31]`. 207 | // `result = daysInMonths[month - 1] + isLeapYear(year)`. 208 | result := add(byte(month, shl(152, 0x1F1C1F1E1F1E1F1F1E1F1E1F)), and(eq(month, 2), flag)) 209 | } 210 | } 211 | 212 | /// @dev Returns the weekday from the unix timestamp. 213 | /// Monday: 1, Tuesday: 2, ....., Sunday: 7. 214 | function weekday(uint256 timestamp) internal pure returns (uint256 result) { 215 | unchecked { 216 | result = ((timestamp / 86400 + 3) % 7) + 1; 217 | } 218 | } 219 | 220 | /// @dev Returns if (`year`,`month`,`day`) is a supported date. 221 | /// - `1970 <= year <= MAX_SUPPORTED_YEAR`. 222 | /// - `1 <= month <= 12`. 223 | /// - `1 <= day <= daysInMonth(year, month)`. 224 | function isSupportedDate( 225 | uint256 year, 226 | uint256 month, 227 | uint256 day 228 | ) internal pure returns (bool result) { 229 | uint256 md = daysInMonth(year, month); 230 | /// @solidity memory-safe-assembly 231 | assembly { 232 | let w := not(0) 233 | result := and( 234 | lt(sub(year, 1970), sub(MAX_SUPPORTED_YEAR, 1969)), 235 | and(lt(add(month, w), 12), lt(add(day, w), md)) 236 | ) 237 | } 238 | } 239 | 240 | /// @dev Returns if (`year`,`month`,`day`,`hour`,`minute`,`second`) is a supported date time. 241 | /// - `1970 <= year <= MAX_SUPPORTED_YEAR`. 242 | /// - `1 <= month <= 12`. 243 | /// - `1 <= day <= daysInMonth(year, month)`. 244 | /// - `hour < 24`. 245 | /// - `minute < 60`. 246 | /// - `second < 60`. 247 | function isSupportedDateTime( 248 | uint256 year, 249 | uint256 month, 250 | uint256 day, 251 | uint256 hour, 252 | uint256 minute, 253 | uint256 second 254 | ) internal pure returns (bool result) { 255 | if (isSupportedDate(year, month, day)) { 256 | /// @solidity memory-safe-assembly 257 | assembly { 258 | result := and(lt(hour, 24), and(lt(minute, 60), lt(second, 60))) 259 | } 260 | } 261 | } 262 | 263 | /// @dev Returns if `epochDay` is a supported unix epoch day. 264 | function isSupportedEpochDay(uint256 epochDay) internal pure returns (bool result) { 265 | unchecked { 266 | result = epochDay < MAX_SUPPORTED_EPOCH_DAY + 1; 267 | } 268 | } 269 | 270 | /// @dev Returns if `timestamp` is a supported unix timestamp. 271 | function isSupportedTimestamp(uint256 timestamp) internal pure returns (bool result) { 272 | unchecked { 273 | result = timestamp < MAX_SUPPORTED_TIMESTAMP + 1; 274 | } 275 | } 276 | 277 | /// @dev Returns the unix timestamp of the given `n`th weekday `wd`, in `month` of `year`. 278 | /// Example: 3rd Friday of Feb 2022 is `nthWeekdayInMonthOfYearTimestamp(2022, 2, 3, 5)` 279 | /// Note: `n` is 1-indexed for traditional consistency. 280 | /// Invalid weekdays (i.e. `wd == 0 || wd > 7`) result in undefined behavior. 281 | function nthWeekdayInMonthOfYearTimestamp( 282 | uint256 year, 283 | uint256 month, 284 | uint256 n, 285 | uint256 wd 286 | ) internal pure returns (uint256 result) { 287 | uint256 d = dateToEpochDay(year, month, 1); 288 | uint256 md = daysInMonth(year, month); 289 | /// @solidity memory-safe-assembly 290 | assembly { 291 | let diff := sub(wd, add(mod(add(d, 3), 7), 1)) 292 | let date := add(mul(sub(n, 1), 7), add(mul(gt(diff, 6), 7), diff)) 293 | result := mul(mul(86400, add(date, d)), and(lt(date, md), iszero(iszero(n)))) 294 | } 295 | } 296 | 297 | /// @dev Returns the unix timestamp of the most recent Monday. 298 | /// Note : If timestamp < 345600 it returns 0 as per UTC it is thursday timestamp 299 | function mondayTimestamp(uint256 timestamp) internal pure returns (uint256 result) { 300 | uint256 t = timestamp; 301 | /// @solidity memory-safe-assembly 302 | assembly { 303 | let day := div(t, 86400) 304 | result := mul(mul(sub(day, mod(add(day, 3), 7)), 86400), gt(t, 345599)) 305 | } 306 | } 307 | 308 | /// @dev Returns whether the unix timestamp falls on a Saturday or Sunday. 309 | /// To check whether it is a week day, just take the negation of the result. 310 | function isWeekEnd(uint256 timestamp) internal pure returns (bool result) { 311 | result = weekday(timestamp) > FRI; 312 | } 313 | 314 | /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ 315 | /* DATE TIME ARITHMETIC OPERATIONS */ 316 | /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ 317 | 318 | /// @dev Adds `numYears` to the unix timestamp, and returns the result. 319 | /// Note: The result will share the same Gregorian calendar month, 320 | /// but different Gregorian calendar years for non-zero `numYears`. 321 | /// If the Gregorian calendar month of the result has less days 322 | /// than the Gregorian calendar month day of the `timestamp`, 323 | /// the result's month day will be the maximum possible value for the month. 324 | /// (e.g. from 29th Feb to 28th Feb) 325 | function addYears(uint256 timestamp, uint256 numYears) internal pure returns (uint256 result) { 326 | unchecked { 327 | (uint256 year, uint256 month, uint256 day) = epochDayToDate(timestamp / 86400); 328 | year += numYears; 329 | uint256 dm = daysInMonth(year, month); 330 | if (day > dm) { 331 | day = dm; 332 | } 333 | result = dateToEpochDay(year, month, day) * 86400 + (timestamp % 86400); 334 | if (result < timestamp) revert Overflow(); 335 | } 336 | } 337 | 338 | /// @dev Adds `numMonths` to the unix timestamp, and returns the result. 339 | /// Note: If the Gregorian calendar month of the result has less days 340 | /// than the Gregorian calendar month day of the `timestamp`, 341 | /// the result's month day will be the maximum possible value for the month. 342 | /// (e.g. from 29th Feb to 28th Feb) 343 | function addMonths(uint256 timestamp, uint256 numMonths) internal pure returns (uint256 result) { 344 | unchecked { 345 | (uint256 year, uint256 month, uint256 day) = epochDayToDate(timestamp / 86400); 346 | month += numMonths; 347 | year += (month - 1) / 12; 348 | month = ((month - 1) % 12) + 1; 349 | uint256 dm = daysInMonth(year, month); 350 | if (day > dm) { 351 | day = dm; 352 | } 353 | result = dateToEpochDay(year, month, day) * 86400 + (timestamp % 86400); 354 | if (result < timestamp) revert Overflow(); 355 | } 356 | } 357 | 358 | /// @dev Adds `numDays` to the unix timestamp, and returns the result. 359 | function addDays(uint256 timestamp, uint256 numDays) internal pure returns (uint256 result) { 360 | unchecked { 361 | result = timestamp + numDays * 86400; 362 | if (result < timestamp) revert Overflow(); 363 | } 364 | } 365 | 366 | /// @dev Adds `numHours` to the unix timestamp, and returns the result. 367 | function addHours(uint256 timestamp, uint256 numHours) internal pure returns (uint256 result) { 368 | unchecked { 369 | result = timestamp + numHours * 3600; 370 | if (result < timestamp) revert Overflow(); 371 | } 372 | } 373 | 374 | /// @dev Adds `numMinutes` to the unix timestamp, and returns the result. 375 | function addMinutes(uint256 timestamp, uint256 numMinutes) internal pure returns (uint256 result) { 376 | unchecked { 377 | result = timestamp + numMinutes * 60; 378 | if (result < timestamp) revert Overflow(); 379 | } 380 | } 381 | 382 | /// @dev Adds `numSeconds` to the unix timestamp, and returns the result. 383 | function addSeconds(uint256 timestamp, uint256 numSeconds) internal pure returns (uint256 result) { 384 | unchecked { 385 | result = timestamp + numSeconds; 386 | if (result < timestamp) revert Overflow(); 387 | } 388 | } 389 | 390 | /// @dev Subtracts `numYears` from the unix timestamp, and returns the result. 391 | /// Note: The result will share the same Gregorian calendar month, 392 | /// but different Gregorian calendar years for non-zero `numYears`. 393 | /// If the Gregorian calendar month of the result has less days 394 | /// than the Gregorian calendar month day of the `timestamp`, 395 | /// the result's month day will be the maximum possible value for the month. 396 | /// (e.g. from 29th Feb to 28th Feb) 397 | function subYears(uint256 timestamp, uint256 numYears) internal pure returns (uint256 result) { 398 | unchecked { 399 | (uint256 year, uint256 month, uint256 day) = epochDayToDate(timestamp / 86400); 400 | year -= numYears; 401 | uint256 dm = daysInMonth(year, month); 402 | if (day > dm) { 403 | day = dm; 404 | } 405 | result = dateToEpochDay(year, month, day) * 86400 + (timestamp % 86400); 406 | if (result > timestamp) revert Underflow(); 407 | } 408 | } 409 | 410 | /// @dev Subtracts `numYears` from the unix timestamp, and returns the result. 411 | /// Note: If the Gregorian calendar month of the result has less days 412 | /// than the Gregorian calendar month day of the `timestamp`, 413 | /// the result's month day will be the maximum possible value for the month. 414 | /// (e.g. from 29th Feb to 28th Feb) 415 | function subMonths(uint256 timestamp, uint256 numMonths) internal pure returns (uint256 result) { 416 | unchecked { 417 | (uint256 year, uint256 month, uint256 day) = epochDayToDate(timestamp / 86400); 418 | uint256 yearMonth = year * 12 + (month - 1) - numMonths; 419 | year = yearMonth / 12; 420 | month = (yearMonth % 12) + 1; 421 | uint256 dm = daysInMonth(year, month); 422 | if (day > dm) { 423 | day = dm; 424 | } 425 | result = dateToEpochDay(year, month, day) * 86400 + (timestamp % 86400); 426 | if (result > timestamp) revert Underflow(); 427 | } 428 | } 429 | 430 | /// @dev Subtracts `numDays` from the unix timestamp, and returns the result. 431 | function subDays(uint256 timestamp, uint256 numDays) internal pure returns (uint256 result) { 432 | unchecked { 433 | result = timestamp - numDays * 86400; 434 | if (result > timestamp) revert Underflow(); 435 | } 436 | } 437 | 438 | /// @dev Subtracts `numHours` from the unix timestamp, and returns the result. 439 | function subHours(uint256 timestamp, uint256 numHours) internal pure returns (uint256 result) { 440 | unchecked { 441 | result = timestamp - numHours * 3600; 442 | if (result > timestamp) revert Underflow(); 443 | } 444 | } 445 | 446 | /// @dev Subtracts `numMinutes` from the unix timestamp, and returns the result. 447 | function subMinutes(uint256 timestamp, uint256 numMinutes) internal pure returns (uint256 result) { 448 | unchecked { 449 | result = timestamp - numMinutes * 60; 450 | if (result > timestamp) revert Underflow(); 451 | } 452 | } 453 | 454 | /// @dev Subtracts `numSeconds` from the unix timestamp, and returns the result. 455 | function subSeconds(uint256 timestamp, uint256 numSeconds) internal pure returns (uint256 result) { 456 | unchecked { 457 | result = timestamp - numSeconds; 458 | if (result > timestamp) revert Underflow(); 459 | } 460 | } 461 | 462 | /// @dev Returns the difference in Gregorian calendar years 463 | /// between `fromTimestamp` and `toTimestamp`. 464 | /// Note: Even if the true time difference is less than a year, 465 | /// the difference can be non-zero is the timestamps are 466 | /// from diffrent Gregorian calendar years 467 | function diffYears(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 result) { 468 | unchecked { 469 | if (fromTimestamp > toTimestamp) revert InvalidDiff(); 470 | (uint256 fromYear, , ) = epochDayToDate(fromTimestamp / 86400); 471 | (uint256 toYear, , ) = epochDayToDate(toTimestamp / 86400); 472 | result = toYear - fromYear; 473 | } 474 | } 475 | 476 | /// @dev Returns the difference in Gregorian calendar months 477 | /// between `fromTimestamp` and `toTimestamp`. 478 | /// Note: Even if the true time difference is less than a month, 479 | /// the difference can be non-zero is the timestamps are 480 | /// from diffrent Gregorian calendar months. 481 | function diffMonths(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 result) { 482 | unchecked { 483 | if (fromTimestamp > toTimestamp) revert InvalidDiff(); 484 | (uint256 fromYear, uint256 fromMonth, ) = epochDayToDate(fromTimestamp / 86400); 485 | (uint256 toYear, uint256 toMonth, ) = epochDayToDate(toTimestamp / 86400); 486 | result = toYear * 12 + toMonth - fromYear * 12 - fromMonth; 487 | } 488 | } 489 | 490 | /// @dev Returns the difference in days between `fromTimestamp` and `toTimestamp`. 491 | function diffDays(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 result) { 492 | unchecked { 493 | if (fromTimestamp > toTimestamp) revert InvalidDiff(); 494 | result = (toTimestamp - fromTimestamp) / 86400; 495 | } 496 | } 497 | 498 | /// @dev Returns the difference in hours between `fromTimestamp` and `toTimestamp`. 499 | function diffHours(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 result) { 500 | unchecked { 501 | if (fromTimestamp > toTimestamp) revert InvalidDiff(); 502 | result = (toTimestamp - fromTimestamp) / 3600; 503 | } 504 | } 505 | 506 | /// @dev Returns the difference in minutes between `fromTimestamp` and `toTimestamp`. 507 | function diffMinutes(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 result) { 508 | unchecked { 509 | if (fromTimestamp > toTimestamp) revert InvalidDiff(); 510 | result = (toTimestamp - fromTimestamp) / 60; 511 | } 512 | } 513 | 514 | /// @dev Returns the difference in seconds between `fromTimestamp` and `toTimestamp`. 515 | function diffSeconds(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 result) { 516 | unchecked { 517 | if (fromTimestamp > toTimestamp) revert InvalidDiff(); 518 | result = toTimestamp - fromTimestamp; 519 | } 520 | } 521 | } -------------------------------------------------------------------------------- /test/DateTimeLib.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "../test/utils/TestPlus.sol"; 5 | import {DateTimeLib} from "../src/DateTimeLib.sol"; 6 | 7 | contract DateTimeLibTest is TestPlus { 8 | struct DateTime { 9 | uint256 year; 10 | uint256 month; 11 | uint256 day; 12 | uint256 hour; 13 | uint256 minute; 14 | uint256 second; 15 | } 16 | 17 | function testDateTimeMaxSupported() public { 18 | DateTime memory d; 19 | assertEq( 20 | DateTimeLib.dateToEpochDay(DateTimeLib.MAX_SUPPORTED_YEAR, 12, 31), 21 | DateTimeLib.MAX_SUPPORTED_EPOCH_DAY 22 | ); 23 | assertEq( 24 | DateTimeLib.dateToTimestamp(DateTimeLib.MAX_SUPPORTED_YEAR, 12, 31) + 86400 - 1, 25 | DateTimeLib.MAX_SUPPORTED_TIMESTAMP 26 | ); 27 | (d.year, d.month, d.day) = DateTimeLib.timestampToDate(DateTimeLib.MAX_SUPPORTED_TIMESTAMP); 28 | assertTrue(d.year == DateTimeLib.MAX_SUPPORTED_YEAR && d.month == 12 && d.day == 31); 29 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(DateTimeLib.MAX_SUPPORTED_EPOCH_DAY); 30 | assertTrue(d.year == DateTimeLib.MAX_SUPPORTED_YEAR && d.month == 12 && d.day == 31); 31 | (d.year, d.month, d.day) = DateTimeLib.timestampToDate(DateTimeLib.MAX_SUPPORTED_TIMESTAMP + 1); 32 | assertFalse(d.year == DateTimeLib.MAX_SUPPORTED_YEAR && d.month == 12 && d.day == 31); 33 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(DateTimeLib.MAX_SUPPORTED_EPOCH_DAY + 1); 34 | assertFalse(d.year == DateTimeLib.MAX_SUPPORTED_YEAR && d.month == 12 && d.day == 31); 35 | } 36 | 37 | function testDateToEpochDay() public { 38 | assertEq(DateTimeLib.dateToEpochDay(1970, 1, 1), 0); 39 | assertEq(DateTimeLib.dateToEpochDay(1970, 1, 2), 1); 40 | assertEq(DateTimeLib.dateToEpochDay(1970, 2, 1), 31); 41 | assertEq(DateTimeLib.dateToEpochDay(1970, 3, 1), 59); 42 | assertEq(DateTimeLib.dateToEpochDay(1970, 4, 1), 90); 43 | assertEq(DateTimeLib.dateToEpochDay(1970, 5, 1), 120); 44 | assertEq(DateTimeLib.dateToEpochDay(1970, 6, 1), 151); 45 | assertEq(DateTimeLib.dateToEpochDay(1970, 7, 1), 181); 46 | assertEq(DateTimeLib.dateToEpochDay(1970, 8, 1), 212); 47 | assertEq(DateTimeLib.dateToEpochDay(1970, 9, 1), 243); 48 | assertEq(DateTimeLib.dateToEpochDay(1970, 10, 1), 273); 49 | assertEq(DateTimeLib.dateToEpochDay(1970, 11, 1), 304); 50 | assertEq(DateTimeLib.dateToEpochDay(1970, 12, 1), 334); 51 | assertEq(DateTimeLib.dateToEpochDay(1970, 12, 31), 364); 52 | assertEq(DateTimeLib.dateToEpochDay(1971, 1, 1), 365); 53 | assertEq(DateTimeLib.dateToEpochDay(1980, 11, 3), 3959); 54 | assertEq(DateTimeLib.dateToEpochDay(2000, 3, 1), 11017); 55 | assertEq(DateTimeLib.dateToEpochDay(2355, 12, 31), 140982); 56 | assertEq(DateTimeLib.dateToEpochDay(99999, 12, 31), 35804721); 57 | assertEq(DateTimeLib.dateToEpochDay(100000, 12, 31), 35805087); 58 | assertEq(DateTimeLib.dateToEpochDay(604800, 2, 29), 220179195); 59 | assertEq(DateTimeLib.dateToEpochDay(1667347200, 2, 29), 608985340227); 60 | assertEq(DateTimeLib.dateToEpochDay(1667952000, 2, 29), 609206238891); 61 | } 62 | 63 | function testFuzzDateToEpochDayGas() public { 64 | unchecked { 65 | uint256 randomness; 66 | uint256 sum; 67 | for (uint256 i; i < 256; ++i) { 68 | randomness = _stepRandomness(randomness); 69 | uint256 year = _bound(randomness, 1970, DateTimeLib.MAX_SUPPORTED_YEAR); 70 | randomness = _stepRandomness(randomness); 71 | uint256 month = _bound(randomness, 1, 12); 72 | uint256 md = DateTimeLib.daysInMonth(year, month); 73 | randomness = _stepRandomness(randomness); 74 | uint256 day = _bound(randomness, 1, md); 75 | uint256 epochDay = DateTimeLib.dateToEpochDay(year, month, day); 76 | sum += epochDay; 77 | } 78 | assertTrue(sum != 0); 79 | } 80 | } 81 | 82 | function testFuzzDateToEpochDayGas2() public { 83 | unchecked { 84 | uint256 randomness; 85 | uint256 sum; 86 | for (uint256 i; i < 256; ++i) { 87 | randomness = _stepRandomness(randomness); 88 | uint256 year = _bound(randomness, 1970, DateTimeLib.MAX_SUPPORTED_YEAR); 89 | randomness = _stepRandomness(randomness); 90 | uint256 month = _bound(randomness, 1, 12); 91 | uint256 md = DateTimeLib.daysInMonth(year, month); 92 | randomness = _stepRandomness(randomness); 93 | uint256 day = _bound(randomness, 1, md); 94 | uint256 epochDay = _dateToEpochDayOriginal2(year, month, day); 95 | sum += epochDay; 96 | } 97 | assertTrue(sum != 0); 98 | } 99 | } 100 | 101 | function testFuzzEpochDayToDateGas() public { 102 | unchecked { 103 | uint256 randomness; 104 | uint256 sum; 105 | for (uint256 i; i < 256; ++i) { 106 | randomness = _stepRandomness(randomness); 107 | uint256 epochDay = _bound(randomness, 0, DateTimeLib.MAX_SUPPORTED_EPOCH_DAY); 108 | (uint256 year, uint256 month, uint256 day) = DateTimeLib.epochDayToDate(epochDay); 109 | sum += year + month + day; 110 | } 111 | assertTrue(sum != 0); 112 | } 113 | } 114 | 115 | function testFuzzEpochDayToDateGas2() public { 116 | unchecked { 117 | uint256 randomness; 118 | uint256 sum; 119 | for (uint256 i; i < 256; ++i) { 120 | randomness = _stepRandomness(randomness); 121 | uint256 epochDay = _bound(randomness, 0, DateTimeLib.MAX_SUPPORTED_EPOCH_DAY); 122 | (uint256 year, uint256 month, uint256 day) = _epochDayToDateOriginal2(epochDay); 123 | sum += year + month + day; 124 | } 125 | assertTrue(sum != 0); 126 | } 127 | } 128 | 129 | function testDateToEpochDayDifferential(DateTime memory d) public { 130 | d.year = _bound(d.year, 1970, DateTimeLib.MAX_SUPPORTED_YEAR); 131 | d.month = _bound(d.month, 1, 12); 132 | d.day = _bound(d.day, 1, DateTimeLib.daysInMonth(d.year, d.month)); 133 | uint256 expectedResult = _dateToEpochDayOriginal(d.year, d.month, d.day); 134 | assertEq(DateTimeLib.dateToEpochDay(d.year, d.month, d.day), expectedResult); 135 | } 136 | 137 | function testDateToEpochDayDifferential2(DateTime memory d) public { 138 | d.year = _bound(d.year, 1970, DateTimeLib.MAX_SUPPORTED_YEAR); 139 | d.month = _bound(d.month, 1, 12); 140 | d.day = _bound(d.day, 1, DateTimeLib.daysInMonth(d.year, d.month)); 141 | uint256 expectedResult = _dateToEpochDayOriginal2(d.year, d.month, d.day); 142 | assertEq(DateTimeLib.dateToEpochDay(d.year, d.month, d.day), expectedResult); 143 | } 144 | 145 | function testEpochDayToDateDifferential(uint256 timestamp) public { 146 | timestamp = _bound(timestamp, 0, DateTimeLib.MAX_SUPPORTED_TIMESTAMP); 147 | DateTime memory a; 148 | DateTime memory b; 149 | (a.year, a.month, a.day) = _epochDayToDateOriginal(timestamp); 150 | (b.year, b.month, b.day) = DateTimeLib.epochDayToDate(timestamp); 151 | assertTrue(a.year == b.year && a.month == b.month && a.day == b.day); 152 | } 153 | 154 | function testEpochDayToDateDifferential2(uint256 timestamp) public { 155 | timestamp = _bound(timestamp, 0, DateTimeLib.MAX_SUPPORTED_TIMESTAMP); 156 | DateTime memory a; 157 | DateTime memory b; 158 | (a.year, a.month, a.day) = _epochDayToDateOriginal2(timestamp); 159 | (b.year, b.month, b.day) = DateTimeLib.epochDayToDate(timestamp); 160 | assertTrue(a.year == b.year && a.month == b.month && a.day == b.day); 161 | } 162 | 163 | function testDaysToDate() public { 164 | DateTime memory d; 165 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(0); 166 | assertTrue(d.year == 1970 && d.month == 1 && d.day == 1); 167 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(31); 168 | assertTrue(d.year == 1970 && d.month == 2 && d.day == 1); 169 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(59); 170 | assertTrue(d.year == 1970 && d.month == 3 && d.day == 1); 171 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(90); 172 | assertTrue(d.year == 1970 && d.month == 4 && d.day == 1); 173 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(120); 174 | assertTrue(d.year == 1970 && d.month == 5 && d.day == 1); 175 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(151); 176 | assertTrue(d.year == 1970 && d.month == 6 && d.day == 1); 177 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(181); 178 | assertTrue(d.year == 1970 && d.month == 7 && d.day == 1); 179 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(212); 180 | assertTrue(d.year == 1970 && d.month == 8 && d.day == 1); 181 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(243); 182 | assertTrue(d.year == 1970 && d.month == 9 && d.day == 1); 183 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(273); 184 | assertTrue(d.year == 1970 && d.month == 10 && d.day == 1); 185 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(304); 186 | assertTrue(d.year == 1970 && d.month == 11 && d.day == 1); 187 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(334); 188 | assertTrue(d.year == 1970 && d.month == 12 && d.day == 1); 189 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(365); 190 | assertTrue(d.year == 1971 && d.month == 1 && d.day == 1); 191 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(10987); 192 | assertTrue(d.year == 2000 && d.month == 1 && d.day == 31); 193 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(18321); 194 | assertTrue(d.year == 2020 && d.month == 2 && d.day == 29); 195 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(156468); 196 | assertTrue(d.year == 2398 && d.month == 5 && d.day == 25); 197 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(35805087); 198 | assertTrue(d.year == 100000 && d.month == 12 && d.day == 31); 199 | } 200 | 201 | function testFuzzEpochDayToDate(uint256 epochDay) public { 202 | DateTime memory d; 203 | (d.year, d.month, d.day) = DateTimeLib.epochDayToDate(epochDay); 204 | assertEq(epochDay, DateTimeLib.dateToEpochDay(d.year, d.month, d.day)); 205 | } 206 | 207 | function testFuzzDateToAndFroEpochDay(DateTime memory a) public { 208 | a.year = _bound(a.year, 1970, DateTimeLib.MAX_SUPPORTED_YEAR); 209 | a.month = _bound(a.month, 1, 12); 210 | uint256 md = DateTimeLib.daysInMonth(a.year, a.month); 211 | a.day = _bound(a.day, 1, md); 212 | uint256 epochDay = DateTimeLib.dateToEpochDay(a.year, a.month, a.day); 213 | DateTime memory b; 214 | (b.year, b.month, b.day) = DateTimeLib.epochDayToDate(epochDay); 215 | assertTrue(a.year == b.year && a.month == b.month && a.day == b.day); 216 | } 217 | 218 | function testFuzzDateTimeToAndFroTimestamp(DateTime memory a) public { 219 | a.year = _bound(a.year, 1970, DateTimeLib.MAX_SUPPORTED_YEAR); 220 | a.month = _bound(a.month, 1, 12); 221 | uint256 md = DateTimeLib.daysInMonth(a.year, a.month); 222 | a.day = _bound(a.day, 1, md); 223 | a.hour = _bound(a.hour, 0, 23); 224 | a.minute = _bound(a.minute, 0, 59); 225 | a.second = _bound(a.second, 0, 59); 226 | uint256 timestamp = DateTimeLib.dateTimeToTimestamp(a.year, a.month, a.day, a.hour, a.minute, a.second); 227 | DateTime memory b; 228 | (b.year, b.month, b.day, b.hour, b.minute, b.second) = DateTimeLib.timestampToDateTime(timestamp); 229 | assertTrue(a.year == b.year && a.month == b.month && a.day == b.day); 230 | assertTrue(a.hour == b.hour && a.minute == b.minute && a.second == b.second); 231 | } 232 | 233 | function testFuzzDateToAndFroEpochDay() public { 234 | unchecked { 235 | uint256 randomness; 236 | for (uint256 i; i < 256; ++i) { 237 | randomness = _stepRandomness(randomness); 238 | uint256 year = _bound(randomness, 1970, DateTimeLib.MAX_SUPPORTED_YEAR); 239 | randomness = _stepRandomness(randomness); 240 | uint256 month = _bound(randomness, 1, 12); 241 | uint256 md = DateTimeLib.daysInMonth(year, month); 242 | randomness = _stepRandomness(randomness); 243 | uint256 day = _bound(randomness, 1, md); 244 | uint256 epochDay = DateTimeLib.dateToEpochDay(year, month, day); 245 | (uint256 y, uint256 m, uint256 d) = DateTimeLib.epochDayToDate(epochDay); 246 | assertTrue(year == y && month == m && day == d); 247 | } 248 | } 249 | } 250 | 251 | function testFuzzDateToAndFroTimestamp() public { 252 | unchecked { 253 | uint256 randomness; 254 | for (uint256 i; i < 256; ++i) { 255 | randomness = _stepRandomness(randomness); 256 | uint256 year = _bound(randomness, 1970, DateTimeLib.MAX_SUPPORTED_YEAR); 257 | randomness = _stepRandomness(randomness); 258 | uint256 month = _bound(randomness, 1, 12); 259 | uint256 md = DateTimeLib.daysInMonth(year, month); 260 | randomness = _stepRandomness(randomness); 261 | uint256 day = _bound(randomness, 1, md); 262 | uint256 timestamp = DateTimeLib.dateToTimestamp(year, month, day); 263 | assertEq(timestamp, DateTimeLib.dateToEpochDay(year, month, day) * 86400); 264 | (uint256 y, uint256 m, uint256 d) = DateTimeLib.timestampToDate(timestamp); 265 | assertTrue(year == y && month == m && day == d); 266 | } 267 | } 268 | } 269 | 270 | function testIsLeapYear() public { 271 | assertTrue(DateTimeLib.isLeapYear(2000)); 272 | assertTrue(DateTimeLib.isLeapYear(2024)); 273 | assertTrue(DateTimeLib.isLeapYear(2048)); 274 | assertTrue(DateTimeLib.isLeapYear(2072)); 275 | assertTrue(DateTimeLib.isLeapYear(2104)); 276 | assertTrue(DateTimeLib.isLeapYear(2128)); 277 | assertTrue(DateTimeLib.isLeapYear(10032)); 278 | assertTrue(DateTimeLib.isLeapYear(10124)); 279 | assertTrue(DateTimeLib.isLeapYear(10296)); 280 | assertTrue(DateTimeLib.isLeapYear(10400)); 281 | assertTrue(DateTimeLib.isLeapYear(10916)); 282 | } 283 | 284 | function testFuzzIsLeapYear(uint256 year) public { 285 | assertEq(DateTimeLib.isLeapYear(year), (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0)); 286 | } 287 | 288 | function testDaysInMonth() public { 289 | assertEq(DateTimeLib.daysInMonth(2022, 1), 31); 290 | assertEq(DateTimeLib.daysInMonth(2022, 2), 28); 291 | assertEq(DateTimeLib.daysInMonth(2022, 3), 31); 292 | assertEq(DateTimeLib.daysInMonth(2022, 4), 30); 293 | assertEq(DateTimeLib.daysInMonth(2022, 5), 31); 294 | assertEq(DateTimeLib.daysInMonth(2022, 6), 30); 295 | assertEq(DateTimeLib.daysInMonth(2022, 7), 31); 296 | assertEq(DateTimeLib.daysInMonth(2022, 8), 31); 297 | assertEq(DateTimeLib.daysInMonth(2022, 9), 30); 298 | assertEq(DateTimeLib.daysInMonth(2022, 10), 31); 299 | assertEq(DateTimeLib.daysInMonth(2022, 11), 30); 300 | assertEq(DateTimeLib.daysInMonth(2022, 12), 31); 301 | assertEq(DateTimeLib.daysInMonth(2024, 1), 31); 302 | assertEq(DateTimeLib.daysInMonth(2024, 2), 29); 303 | assertEq(DateTimeLib.daysInMonth(1900, 2), 28); 304 | } 305 | 306 | function testFuzzDaysInMonth(uint256 year, uint256 month) public { 307 | month = _bound(month, 1, 12); 308 | if (DateTimeLib.isLeapYear(year) && month == 2) { 309 | assertEq(DateTimeLib.daysInMonth(year, month), 29); 310 | } else if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) { 311 | assertEq(DateTimeLib.daysInMonth(year, month), 31); 312 | } else if (month == 2) { 313 | assertEq(DateTimeLib.daysInMonth(year, month), 28); 314 | } else { 315 | assertEq(DateTimeLib.daysInMonth(year, month), 30); 316 | } 317 | } 318 | 319 | function testWeekday() public { 320 | assertEq(DateTimeLib.weekday(1), 4); 321 | assertEq(DateTimeLib.weekday(86400), 5); 322 | assertEq(DateTimeLib.weekday(86401), 5); 323 | assertEq(DateTimeLib.weekday(172800), 6); 324 | assertEq(DateTimeLib.weekday(259200), 7); 325 | assertEq(DateTimeLib.weekday(345600), 1); 326 | assertEq(DateTimeLib.weekday(432000), 2); 327 | assertEq(DateTimeLib.weekday(518400), 3); 328 | } 329 | 330 | function testFuzzDayOfWeek() public { 331 | uint256 timestamp = 0; 332 | uint256 weekday = 3; 333 | unchecked { 334 | for (uint256 i = 0; i < 1000; ++i) { 335 | assertEq(DateTimeLib.weekday(timestamp) - 1, weekday); 336 | timestamp += 86400; 337 | weekday = (weekday + 1) % 7; 338 | } 339 | } 340 | } 341 | 342 | function testIsSupportedDateTrue() public { 343 | assertTrue(DateTimeLib.isSupportedDate(1970, 1, 1)); 344 | assertTrue(DateTimeLib.isSupportedDate(1971, 5, 31)); 345 | assertTrue(DateTimeLib.isSupportedDate(1971, 6, 30)); 346 | assertTrue(DateTimeLib.isSupportedDate(1971, 12, 31)); 347 | assertTrue(DateTimeLib.isSupportedDate(1972, 2, 28)); 348 | assertTrue(DateTimeLib.isSupportedDate(1972, 4, 30)); 349 | assertTrue(DateTimeLib.isSupportedDate(1972, 5, 31)); 350 | assertTrue(DateTimeLib.isSupportedDate(2000, 2, 29)); 351 | assertTrue(DateTimeLib.isSupportedDate(DateTimeLib.MAX_SUPPORTED_YEAR, 5, 31)); 352 | } 353 | 354 | function testIsSupportedDateFalse() public { 355 | assertFalse(DateTimeLib.isSupportedDate(0, 0, 0)); 356 | assertFalse(DateTimeLib.isSupportedDate(1970, 0, 0)); 357 | assertFalse(DateTimeLib.isSupportedDate(1970, 1, 0)); 358 | assertFalse(DateTimeLib.isSupportedDate(1969, 1, 1)); 359 | assertFalse(DateTimeLib.isSupportedDate(1800, 1, 1)); 360 | assertFalse(DateTimeLib.isSupportedDate(1970, 13, 1)); 361 | assertFalse(DateTimeLib.isSupportedDate(1700, 13, 1)); 362 | assertFalse(DateTimeLib.isSupportedDate(1970, 15, 32)); 363 | assertFalse(DateTimeLib.isSupportedDate(1970, 1, 32)); 364 | assertFalse(DateTimeLib.isSupportedDate(1970, 13, 1)); 365 | assertFalse(DateTimeLib.isSupportedDate(1879, 1, 1)); 366 | assertFalse(DateTimeLib.isSupportedDate(1970, 4, 31)); 367 | assertFalse(DateTimeLib.isSupportedDate(1970, 6, 31)); 368 | assertFalse(DateTimeLib.isSupportedDate(1970, 7, 32)); 369 | assertFalse(DateTimeLib.isSupportedDate(2000, 2, 30)); 370 | assertFalse(DateTimeLib.isSupportedDate(DateTimeLib.MAX_SUPPORTED_YEAR + 1, 5, 31)); 371 | assertFalse(DateTimeLib.isSupportedDate(type(uint256).max, 5, 31)); 372 | } 373 | 374 | function testFuzzIsSupportedDateTime(DateTime memory a) public { 375 | a.month = _bound(a.month, 0, 20); 376 | a.day = _bound(a.day, 0, 50); 377 | a.hour = _bound(a.hour, 0, 50); 378 | a.minute = _bound(a.minute, 0, 100); 379 | a.second = _bound(a.second, 0, 100); 380 | bool isSupported = (1970 <= a.year && a.year <= DateTimeLib.MAX_SUPPORTED_YEAR) && 381 | (1 <= a.month && a.month <= 12) && 382 | (1 <= a.day && a.day <= DateTimeLib.daysInMonth(a.year, a.month)) && 383 | (a.hour < 24) && 384 | (a.minute < 60) && 385 | (a.second < 60); 386 | assertEq(DateTimeLib.isSupportedDateTime(a.year, a.month, a.day, a.hour, a.minute, a.second), isSupported); 387 | } 388 | 389 | function testIsSupportedEpochDayTrue() public { 390 | assertTrue(DateTimeLib.isSupportedEpochDay(0)); 391 | assertTrue(DateTimeLib.isSupportedEpochDay(DateTimeLib.MAX_SUPPORTED_EPOCH_DAY)); 392 | } 393 | 394 | function testIsSupportedEpochDayFalse() public { 395 | assertFalse(DateTimeLib.isSupportedEpochDay(DateTimeLib.MAX_SUPPORTED_EPOCH_DAY + 1)); 396 | assertFalse(DateTimeLib.isSupportedEpochDay(DateTimeLib.MAX_SUPPORTED_EPOCH_DAY + 2)); 397 | } 398 | 399 | function testIsSupportedTimestampTrue() public { 400 | assertTrue(DateTimeLib.isSupportedTimestamp(0)); 401 | assertTrue(DateTimeLib.isSupportedTimestamp(DateTimeLib.MAX_SUPPORTED_TIMESTAMP)); 402 | } 403 | 404 | function testIsSupportedTimestampFalse() public { 405 | assertFalse(DateTimeLib.isSupportedTimestamp(DateTimeLib.MAX_SUPPORTED_TIMESTAMP + 1)); 406 | assertFalse(DateTimeLib.isSupportedTimestamp(DateTimeLib.MAX_SUPPORTED_TIMESTAMP + 2)); 407 | } 408 | 409 | function testNthWeekdayInMonthOfYearTimestamp() public { 410 | uint256 wd; 411 | // 1st 2nd 3rd 4th monday in Novermber 2022. 412 | wd = DateTimeLib.MON; 413 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 11, 1, wd), 1667779200); 414 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 11, 2, wd), 1668384000); 415 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 11, 3, wd), 1668988800); 416 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 11, 4, wd), 1669593600); 417 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 11, 5, wd), 0); 418 | 419 | // 1st... 5th Wednesday in Novermber 2022. 420 | wd = DateTimeLib.WED; 421 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 11, 1, wd), 1667347200); 422 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 11, 2, wd), 1667952000); 423 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 11, 3, wd), 1668556800); 424 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 11, 4, wd), 1669161600); 425 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 11, 5, wd), 1669766400); 426 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 11, 6, wd), 0); 427 | 428 | // 1st... 5th Friday in December 2022. 429 | wd = DateTimeLib.FRI; 430 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 12, 1, wd), 1669939200); 431 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 12, 2, wd), 1670544000); 432 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 12, 3, wd), 1671148800); 433 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 12, 4, wd), 1671753600); 434 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 12, 5, wd), 1672358400); 435 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2022, 12, 6, wd), 0); 436 | 437 | // 1st... 5th Sunday in January 2023. 438 | wd = DateTimeLib.SUN; 439 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2023, 1, 1, wd), 1672531200); 440 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2023, 1, 2, wd), 1673136000); 441 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2023, 1, 3, wd), 1673740800); 442 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2023, 1, 4, wd), 1674345600); 443 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2023, 1, 5, wd), 1674950400); 444 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(2023, 1, 6, wd), 0); 445 | } 446 | 447 | function testFuzzNthWeekdayInMonthOfYearTimestamp( 448 | uint256 year, 449 | uint256 month, 450 | uint256 n, 451 | uint256 weekday 452 | ) public { 453 | unchecked { 454 | year = _bound(year, 1970, DateTimeLib.MAX_SUPPORTED_YEAR); 455 | month = _bound(month, 1, 12); 456 | n = _bound(n, 1, 10); 457 | weekday = _bound(weekday, 1, 7); 458 | // Count number of weekdays for the month in the year. 459 | uint256 md = DateTimeLib.daysInMonth(year, month); 460 | uint256 timestamp = DateTimeLib.dateToTimestamp(year, month, 1); 461 | uint256 m; 462 | uint256 found; 463 | for (uint256 i; i < md; ) { 464 | if (DateTimeLib.weekday(timestamp) == weekday) { 465 | if (++m == n) { 466 | found = 1; 467 | break; 468 | } 469 | } 470 | if (m == 0) { 471 | timestamp += 86400; 472 | i += 1; 473 | } else { 474 | timestamp += 86400 * 7; 475 | i += 7; 476 | } 477 | } 478 | assertEq(DateTimeLib.nthWeekdayInMonthOfYearTimestamp(year, month, n, weekday), found * timestamp); 479 | } 480 | } 481 | 482 | function testMondayTimestamp() public { 483 | // Thursday 01 January 1970 -> 0 484 | assertEq(DateTimeLib.mondayTimestamp(0), 0); 485 | // Friday 02 January 1970 -> 86400 486 | assertEq(DateTimeLib.mondayTimestamp(86400), 0); 487 | // Saturday 03 January 1970 -> 172800 488 | assertEq(DateTimeLib.mondayTimestamp(172800), 0); 489 | // Sunday 04 January 1970 -> 259200 490 | assertEq(DateTimeLib.mondayTimestamp(259200), 0); 491 | // Monday 05 January 19700 -> 345600 492 | assertEq(DateTimeLib.mondayTimestamp(345600), 345600); 493 | // Monday 07 Novermber 2022 -> 1667779200 494 | assertEq(DateTimeLib.mondayTimestamp(1667779200), 1667779200); 495 | // Sunday 06 Novermber 2022 -> 1667692800 496 | assertEq(DateTimeLib.mondayTimestamp(1667692800), 1667174400); 497 | // Saturday 05 Novermber 2022 -> 1667606400 498 | assertEq(DateTimeLib.mondayTimestamp(1667606400), 1667174400); 499 | // Friday 04 Novermber 2022 -> 1667520000 500 | assertEq(DateTimeLib.mondayTimestamp(1667520000), 1667174400); 501 | // Thursday 03 Novermber 2022 -> 1667433600 502 | assertEq(DateTimeLib.mondayTimestamp(1667433600), 1667174400); 503 | // Wednesday 02 Novermber 2022 -> 1667347200 504 | assertEq(DateTimeLib.mondayTimestamp(1667347200), 1667174400); 505 | // Tuesday 01 Novermber 2022 -> 1667260800 506 | assertEq(DateTimeLib.mondayTimestamp(1667260800), 1667174400); 507 | // Monday 01 Novermber 2022 -> 1667260800 508 | assertEq(DateTimeLib.mondayTimestamp(1667174400), 1667174400); 509 | } 510 | 511 | function testFuzzMondayTimestamp(uint256 timestamp) public { 512 | uint256 day = timestamp / 86400; 513 | uint256 weekday = (day + 3) % 7; 514 | assertEq(DateTimeLib.mondayTimestamp(timestamp), timestamp > 345599 ? (day - weekday) * 86400 : 0); 515 | } 516 | 517 | function testFuzzIsWeekEnd(uint256 timestamp) public { 518 | timestamp = _bound(timestamp, 0, DateTimeLib.MAX_SUPPORTED_TIMESTAMP); 519 | uint256 weekday = DateTimeLib.weekday(timestamp); 520 | assertEq(DateTimeLib.isWeekEnd(timestamp), weekday == DateTimeLib.SAT || weekday == DateTimeLib.SUN); 521 | } 522 | 523 | function testFuzzAddSubDiffYears(uint256 timestamp, uint256 numYears) public { 524 | uint256 maxNumYears = 1000000; 525 | numYears = _bound(numYears, 0, maxNumYears); 526 | timestamp = _bound(timestamp, 0, DateTimeLib.MAX_SUPPORTED_TIMESTAMP - maxNumYears * 366 * 86400); 527 | uint256 result = DateTimeLib.addYears(timestamp, numYears); 528 | DateTime memory a; 529 | DateTime memory b; 530 | (a.year, a.month, a.day, a.hour, a.minute, a.second) = DateTimeLib.timestampToDateTime(timestamp); 531 | (b.year, b.month, b.day, b.hour, b.minute, b.second) = DateTimeLib.timestampToDateTime(result); 532 | if (numYears != 0) assertTrue(a.year != b.year); 533 | if (a.day <= 28) assertEq(a.day, b.day); 534 | assertTrue(a.month == b.month); 535 | assertTrue(a.hour == b.hour && a.minute == b.minute && a.second == b.second); 536 | uint256 diff = DateTimeLib.diffYears(timestamp, result); 537 | assertTrue(diff == numYears); 538 | result = DateTimeLib.subYears(result, numYears); 539 | (b.year, b.month, b.day, b.hour, b.minute, b.second) = DateTimeLib.timestampToDateTime(result); 540 | assertTrue(a.year == b.year && a.month == b.month); 541 | assertTrue(a.hour == b.hour && a.minute == b.minute && a.second == b.second); 542 | } 543 | 544 | function testDateTimeArithmeticReverts() public { 545 | vm.expectRevert(DateTimeLib.Overflow.selector); 546 | DateTimeLib.addYears(2**128 - 1, 2**255 - 1); 547 | vm.expectRevert(DateTimeLib.Underflow.selector); 548 | DateTimeLib.subYears(2**128 - 1, 2**255 - 1); 549 | vm.expectRevert(DateTimeLib.InvalidDiff.selector); 550 | DateTimeLib.diffYears(2**128 - 1, 2**127 - 1); 551 | 552 | vm.expectRevert(DateTimeLib.Overflow.selector); 553 | DateTimeLib.addMonths(2**128 - 1, 2**255 - 1); 554 | vm.expectRevert(DateTimeLib.Underflow.selector); 555 | DateTimeLib.subMonths(2**128 - 1, 2**255 - 1); 556 | vm.expectRevert(DateTimeLib.InvalidDiff.selector); 557 | DateTimeLib.diffMonths(2**128 - 1, 2**127 - 1); 558 | 559 | vm.expectRevert(DateTimeLib.Overflow.selector); 560 | DateTimeLib.addDays(2**128 - 1, 2**255 - 1); 561 | vm.expectRevert(DateTimeLib.Underflow.selector); 562 | DateTimeLib.subDays(2**128 - 1, 2**255 - 1); 563 | vm.expectRevert(DateTimeLib.InvalidDiff.selector); 564 | DateTimeLib.diffDays(2**128 - 1, 2**127 - 1); 565 | 566 | vm.expectRevert(DateTimeLib.Overflow.selector); 567 | DateTimeLib.addHours(2**128 - 1, 2**255 - 1); 568 | vm.expectRevert(DateTimeLib.Underflow.selector); 569 | DateTimeLib.subHours(2**128 - 1, 2**255 - 1); 570 | vm.expectRevert(DateTimeLib.InvalidDiff.selector); 571 | DateTimeLib.diffHours(2**128 - 1, 2**127 - 1); 572 | 573 | vm.expectRevert(DateTimeLib.Overflow.selector); 574 | DateTimeLib.addMinutes(2**128 - 1, 2**255 - 1); 575 | vm.expectRevert(DateTimeLib.Underflow.selector); 576 | DateTimeLib.subMinutes(2**128 - 1, 2**255 - 1); 577 | vm.expectRevert(DateTimeLib.InvalidDiff.selector); 578 | DateTimeLib.diffMinutes(2**128 - 1, 2**127 - 1); 579 | 580 | vm.expectRevert(DateTimeLib.Overflow.selector); 581 | DateTimeLib.addSeconds(2**128 - 1, 2**255 - 1); 582 | vm.expectRevert(DateTimeLib.Underflow.selector); 583 | DateTimeLib.subSeconds(2**128 - 1, 2**255 - 1); 584 | vm.expectRevert(DateTimeLib.InvalidDiff.selector); 585 | DateTimeLib.diffSeconds(2**128 - 1, 2**127 - 1); 586 | } 587 | 588 | function testFuzzAddSubDiffMonths(uint256 timestamp, uint256 numMonths) public { 589 | uint256 maxNumMonths = 1000000; 590 | numMonths = _bound(numMonths, 0, maxNumMonths); 591 | timestamp = _bound(timestamp, 0, DateTimeLib.MAX_SUPPORTED_TIMESTAMP - maxNumMonths * 32 * 86400); 592 | uint256 result = DateTimeLib.addMonths(timestamp, numMonths); 593 | DateTime memory a; 594 | DateTime memory b; 595 | (a.year, a.month, a.day, a.hour, a.minute, a.second) = DateTimeLib.timestampToDateTime(timestamp); 596 | (b.year, b.month, b.day, b.hour, b.minute, b.second) = DateTimeLib.timestampToDateTime(result); 597 | if (numMonths != 0) assertTrue(a.year != b.year || a.month != b.month); 598 | if (a.day <= 28) assertEq(a.day, b.day); 599 | assertTrue(a.hour == b.hour && a.minute == b.minute && a.second == b.second); 600 | uint256 diff = DateTimeLib.diffMonths(timestamp, result); 601 | assertTrue(diff == numMonths); 602 | result = DateTimeLib.subMonths(result, numMonths); 603 | (b.year, b.month, b.day, b.hour, b.minute, b.second) = DateTimeLib.timestampToDateTime(result); 604 | assertTrue(a.year == b.year && a.month == b.month); 605 | assertTrue(a.hour == b.hour && a.minute == b.minute && a.second == b.second); 606 | } 607 | 608 | function testFuzzAddSubDiffDays(uint256 timestamp, uint256 numDays) public { 609 | uint256 maxNumDays = 100000000; 610 | numDays = _bound(numDays, 0, maxNumDays); 611 | timestamp = _bound(timestamp, 0, DateTimeLib.MAX_SUPPORTED_TIMESTAMP - maxNumDays * 86400); 612 | uint256 result = DateTimeLib.addDays(timestamp, numDays); 613 | DateTime memory a; 614 | DateTime memory b; 615 | (a.year, a.month, a.day, a.hour, a.minute, a.second) = DateTimeLib.timestampToDateTime(timestamp); 616 | (b.year, b.month, b.day, b.hour, b.minute, b.second) = DateTimeLib.timestampToDateTime(result); 617 | if (numDays != 0) assertTrue(a.year != b.year || a.month != b.month || a.day != b.day); 618 | assertTrue(a.hour == b.hour && a.minute == b.minute && a.second == b.second); 619 | uint256 diff = DateTimeLib.diffDays(timestamp, result); 620 | assertTrue(diff == numDays); 621 | result = DateTimeLib.subDays(result, numDays); 622 | (b.year, b.month, b.day, b.hour, b.minute, b.second) = DateTimeLib.timestampToDateTime(result); 623 | assertTrue(a.year == b.year && a.month == b.month); 624 | assertTrue(a.hour == b.hour && a.minute == b.minute && a.second == b.second); 625 | } 626 | 627 | function testFuzzAddSubDiffHours(uint256 timestamp, uint256 numHours) public { 628 | uint256 maxNumHours = 10000000000; 629 | numHours = _bound(numHours, 0, maxNumHours); 630 | timestamp = _bound(timestamp, 0, DateTimeLib.MAX_SUPPORTED_TIMESTAMP - maxNumHours * 3600); 631 | uint256 result = DateTimeLib.addHours(timestamp, numHours); 632 | DateTime memory a; 633 | DateTime memory b; 634 | (a.year, a.month, a.day, a.hour, a.minute, a.second) = DateTimeLib.timestampToDateTime(timestamp); 635 | (b.year, b.month, b.day, b.hour, b.minute, b.second) = DateTimeLib.timestampToDateTime(result); 636 | if (numHours != 0) assertTrue(a.year != b.year || a.month != b.month || a.day != b.day || a.hour != b.hour); 637 | assertTrue(a.minute == b.minute && a.second == b.second); 638 | uint256 diff = DateTimeLib.diffHours(timestamp, result); 639 | assertTrue(diff == numHours); 640 | result = DateTimeLib.subHours(result, numHours); 641 | (b.year, b.month, b.day, b.hour, b.minute, b.second) = DateTimeLib.timestampToDateTime(result); 642 | assertTrue(a.year == b.year && a.month == b.month); 643 | assertTrue(a.hour == b.hour && a.minute == b.minute && a.second == b.second); 644 | } 645 | 646 | function testFuzzAddSubDiffMinutes(uint256 timestamp, uint256 numMinutes) public { 647 | uint256 maxNumMinutes = 10000000000; 648 | numMinutes = _bound(numMinutes, 0, maxNumMinutes); 649 | timestamp = _bound(timestamp, 0, DateTimeLib.MAX_SUPPORTED_TIMESTAMP - maxNumMinutes * 60); 650 | uint256 result = DateTimeLib.addMinutes(timestamp, numMinutes); 651 | DateTime memory a; 652 | DateTime memory b; 653 | (a.year, a.month, a.day, a.hour, a.minute, a.second) = DateTimeLib.timestampToDateTime(timestamp); 654 | (b.year, b.month, b.day, b.hour, b.minute, b.second) = DateTimeLib.timestampToDateTime(result); 655 | if (numMinutes != 0) 656 | assertTrue( 657 | (a.year != b.year || a.month != b.month || a.day != b.day) || (a.hour != b.hour || a.minute != b.minute) 658 | ); 659 | assertTrue(a.second == b.second); 660 | uint256 diff = DateTimeLib.diffMinutes(timestamp, result); 661 | assertTrue(diff == numMinutes); 662 | result = DateTimeLib.subMinutes(result, numMinutes); 663 | (b.year, b.month, b.day, b.hour, b.minute, b.second) = DateTimeLib.timestampToDateTime(result); 664 | assertTrue(a.year == b.year && a.month == b.month); 665 | assertTrue(a.hour == b.hour && a.minute == b.minute && a.second == b.second); 666 | } 667 | 668 | function testFuzzAddSubDiffSeconds(uint256 timestamp, uint256 numSeconds) public { 669 | uint256 maxNumSeconds = 1000000000000; 670 | numSeconds = _bound(numSeconds, 0, maxNumSeconds); 671 | timestamp = _bound(timestamp, 0, DateTimeLib.MAX_SUPPORTED_TIMESTAMP - maxNumSeconds); 672 | uint256 result = DateTimeLib.addSeconds(timestamp, numSeconds); 673 | DateTime memory a; 674 | DateTime memory b; 675 | (a.year, a.month, a.day, a.hour, a.minute, a.second) = DateTimeLib.timestampToDateTime(timestamp); 676 | (b.year, b.month, b.day, b.hour, b.minute, b.second) = DateTimeLib.timestampToDateTime(result); 677 | if (numSeconds != 0) 678 | assertTrue( 679 | (a.year != b.year || a.month != b.month || a.day != b.day) || 680 | (a.hour != b.hour || a.minute != b.minute || a.second != b.second) 681 | ); 682 | uint256 diff = DateTimeLib.diffSeconds(timestamp, result); 683 | assertTrue(diff == numSeconds); 684 | result = DateTimeLib.subSeconds(result, numSeconds); 685 | (b.year, b.month, b.day, b.hour, b.minute, b.second) = DateTimeLib.timestampToDateTime(result); 686 | assertTrue(a.year == b.year && a.month == b.month); 687 | assertTrue(a.hour == b.hour && a.minute == b.minute && a.second == b.second); 688 | } 689 | 690 | function _dateToEpochDayOriginal( 691 | uint256 year, 692 | uint256 month, 693 | uint256 day 694 | ) internal pure returns (uint256) { 695 | unchecked { 696 | if (month <= 2) { 697 | year -= 1; 698 | } 699 | uint256 era = year / 400; 700 | uint256 yoe = year - era * 400; 701 | uint256 doy = (153 * (month > 2 ? month - 3 : month + 9) + 2) / 5 + day - 1; 702 | uint256 doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; 703 | return era * 146097 + doe - 719468; 704 | } 705 | } 706 | 707 | function _dateToEpochDayOriginal2( 708 | uint256 year, 709 | uint256 month, 710 | uint256 day 711 | ) internal pure returns (uint256 _days) { 712 | unchecked { 713 | int256 _year = int256(year); 714 | int256 _month = int256(month); 715 | int256 _day = int256(day); 716 | 717 | int256 _m = (_month - 14) / 12; 718 | int256 __days = _day - 719 | 32075 + 720 | ((1461 * (_year + 4800 + _m)) / 4) + 721 | ((367 * (_month - 2 - _m * 12)) / 12) - 722 | ((3 * ((_year + 4900 + _m) / 100)) / 4) - 723 | 2440588; 724 | 725 | _days = uint256(__days); 726 | } 727 | } 728 | 729 | function _epochDayToDateOriginal(uint256 timestamp) 730 | internal 731 | pure 732 | returns ( 733 | uint256 year, 734 | uint256 month, 735 | uint256 day 736 | ) 737 | { 738 | unchecked { 739 | timestamp += 719468; 740 | uint256 era = timestamp / 146097; 741 | uint256 doe = timestamp - era * 146097; 742 | uint256 yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; 743 | year = yoe + era * 400; 744 | uint256 doy = doe - (365 * yoe + yoe / 4 - yoe / 100); 745 | uint256 mp = (5 * doy + 2) / 153; 746 | day = doy - (153 * mp + 2) / 5 + 1; 747 | month = mp < 10 ? mp + 3 : mp - 9; 748 | if (month <= 2) { 749 | year += 1; 750 | } 751 | } 752 | } 753 | 754 | function _epochDayToDateOriginal2(uint256 _days) 755 | internal 756 | pure 757 | returns ( 758 | uint256 year, 759 | uint256 month, 760 | uint256 day 761 | ) 762 | { 763 | unchecked { 764 | int256 __days = int256(_days); 765 | 766 | int256 L = __days + 68569 + 2440588; 767 | int256 N = (4 * L) / 146097; 768 | L = L - (146097 * N + 3) / 4; 769 | int256 _year = (4000 * (L + 1)) / 1461001; 770 | L = L - (1461 * _year) / 4 + 31; 771 | int256 _month = (80 * L) / 2447; 772 | int256 _day = L - (2447 * _month) / 80; 773 | L = _month / 11; 774 | _month = _month + 2 - 12 * L; 775 | _year = 100 * (N - 49) + _year + L; 776 | 777 | year = uint256(_year); 778 | month = uint256(_month); 779 | day = uint256(_day); 780 | } 781 | } 782 | } -------------------------------------------------------------------------------- /test/utils/TestPlus.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import "forge-std/Test.sol"; 4 | 5 | /// @author Modified from (https://github.com/Vectorized/solady/blob/main/test/utils/TestPlus.sol) 6 | contract TestPlus is Test { 7 | modifier brutalizeMemory() { 8 | /// @solidity memory-safe-assembly 9 | assembly { 10 | let offset := mload(0x40) // Start the offset at the free memory pointer. 11 | calldatacopy(offset, 0, calldatasize()) 12 | 13 | // Fill the 64 bytes of scratch space with garbage. 14 | mstore(0x00, xor(gas(), calldatasize())) 15 | mstore(0x20, xor(caller(), keccak256(offset, calldatasize()))) 16 | mstore(0x00, keccak256(0x00, 0x40)) 17 | mstore(0x20, keccak256(0x00, 0x40)) 18 | 19 | let size := 0x40 // Start with 2 slots. 20 | mstore(offset, mload(0x00)) 21 | mstore(add(offset, 0x20), mload(0x20)) 22 | 23 | // prettier-ignore 24 | for { let i := add(11, and(mload(0x00), 1)) } 1 {} { 25 | let nextOffset := add(offset, size) 26 | // Duplicate the data. 27 | pop( 28 | staticcall( 29 | gas(), // Pass along all the gas in the call. 30 | 0x04, // Call the identity precompile address. 31 | offset, // Offset is the bytes' pointer. 32 | size, // We want to pass the length of the bytes. 33 | nextOffset, // Store the return value at the next offset. 34 | size // Since the precompile just returns its input, we reuse size. 35 | ) 36 | ) 37 | // Duplicate the data again. 38 | returndatacopy(add(nextOffset, size), 0, size) 39 | offset := nextOffset 40 | size := mul(2, size) 41 | 42 | i := sub(i, 1) 43 | // prettier-ignore 44 | if iszero(i) { break } 45 | } 46 | } 47 | 48 | _; 49 | } 50 | 51 | function _roundUpFreeMemoryPointer() internal pure { 52 | /// @solidity memory-safe-assembly 53 | assembly { 54 | mstore(0x40, and(add(mload(0x40), 31), not(31))) 55 | } 56 | } 57 | 58 | function _brutalizeFreeMemoryStart() internal pure { 59 | bool failed; 60 | /// @solidity memory-safe-assembly 61 | assembly { 62 | let freeMemoryPointer := mload(0x40) 63 | // This ensures that the memory allocated is 32-byte aligned. 64 | if and(freeMemoryPointer, 31) { 65 | failed := 1 66 | } 67 | // Write some garbage to the free memory. 68 | // If the allocated memory is insufficient, this will change the 69 | // decoded string and cause the subsequent asserts to fail. 70 | mstore(freeMemoryPointer, keccak256(0x00, 0x60)) 71 | } 72 | if (failed) revert("Free memory pointer `0x40` not 32-byte word aligned!"); 73 | } 74 | 75 | function _stepRandomness(uint256 randomness) internal pure returns (uint256 nextRandomness) { 76 | /// @solidity memory-safe-assembly 77 | assembly { 78 | mstore(0x00, randomness) 79 | nextRandomness := keccak256(0x00, 0x20) 80 | } 81 | } 82 | 83 | function _checkZeroRightPadded(string memory s) internal pure { 84 | bool failed; 85 | /// @solidity memory-safe-assembly 86 | assembly { 87 | let lastWord := mload(add(add(s, 0x20), and(mload(s), not(31)))) 88 | let remainder := and(mload(s), 31) 89 | if remainder { 90 | if shl(mul(8, remainder), lastWord) { 91 | failed := 1 92 | } 93 | } 94 | } 95 | if (failed) revert("String not zero right padded!"); 96 | } 97 | 98 | function _checkZeroRightPadded(bytes memory s) internal pure { 99 | bool failed; 100 | /// @solidity memory-safe-assembly 101 | assembly { 102 | let lastWord := mload(add(add(s, 0x20), and(mload(s), not(31)))) 103 | let remainder := and(mload(s), 31) 104 | if remainder { 105 | if shl(mul(8, remainder), lastWord) { 106 | failed := 1 107 | } 108 | } 109 | } 110 | if (failed) revert("Bytes not zero right padded!"); 111 | } 112 | 113 | /// @dev Adapted from: 114 | /// https://github.com/foundry-rs/forge-std/blob/ff4bf7db008d096ea5a657f2c20516182252a3ed/src/StdUtils.sol#L10 115 | /// Differentially fuzzed tested against the original implementation. 116 | function _bound(uint256 x, uint256 min, uint256 max) internal pure override returns (uint256 result) { 117 | require(min <= max, "_bound(uint256,uint256,uint256): Max is less than min."); 118 | 119 | /// @solidity memory-safe-assembly 120 | assembly { 121 | // prettier-ignore 122 | for {} 1 {} { 123 | // If `x` is between `min` and `max`, return `x` directly. 124 | // This is to ensure that dictionary values 125 | // do not get shifted if the min is nonzero. 126 | // More info: https://github.com/foundry-rs/forge-std/issues/188 127 | if iszero(or(lt(x, min), gt(x, max))) { 128 | result := x 129 | break 130 | } 131 | 132 | let size := add(sub(max, min), 1) 133 | if and(iszero(gt(x, 3)), gt(size, x)) { 134 | result := add(min, x) 135 | break 136 | } 137 | 138 | let w := not(0) 139 | if and(iszero(lt(x, sub(0, 4))), gt(size, sub(w, x))) { 140 | result := sub(max, sub(w, x)) 141 | break 142 | } 143 | 144 | // Otherwise, wrap x into the range [min, max], 145 | // i.e. the range is inclusive. 146 | if iszero(lt(x, max)) { 147 | let d := sub(x, max) 148 | let r := mod(d, size) 149 | if iszero(r) { 150 | result := max 151 | break 152 | } 153 | result := add(add(min, r), w) 154 | break 155 | } 156 | let d := sub(min, x) 157 | let r := mod(d, size) 158 | if iszero(r) { 159 | result := min 160 | break 161 | } 162 | result := add(sub(max, r), 1) 163 | break 164 | } 165 | } 166 | } 167 | } 168 | --------------------------------------------------------------------------------