├── .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 |
--------------------------------------------------------------------------------