Status: I'm currently trying to get this library audited, so don't use in production mode yet. Feedback welcome.
A gas-efficient Solidity date and time library.
Instead of using loops and lookup tables, this date conversions library uses formulae to convert year/month/day hour:minute:second to a Unix timestamp and back.
If you find this library useful for your project, especially commercial projects, please donate to 0xb6dAC2C5A0222f6794265249ACE15568B750c2d1. I hope to cover my cost to get this library audited.
If there is sufficient interest and donations, this library will be extended (or built upon) to handle financial date calculations like cashflow generation, days basis (ACT/ACT, ACT/365, 30/360, ...), regional holidays in a shared smart contract database, potentially with a Decentralised Autonomous Organisation as the keeper of this database.
- History
- Deployment
- Questions And Answers
- Conventions
- Functions
- _daysFromDate
- _daysToDate
- timestampFromDate
- timestampFromDateTime
- timestampToDate
- timestampToDateTime
- isLeapYear
- _isLeapYear
- isWeekDay
- isWeekEnd
- getDaysInMonth
- _getDaysInMonth
- getDayOfWeek
- getYear
- getMonth
- getDay
- getHour
- getMinute
- getSecond
- addYears
- addMonths
- addDays
- addHours
- addMinutes
- addSeconds
- subYears
- subMonths
- subDays
- subHours
- subMinutes
- subSeconds
- diffYears
- diffMonths
- diffDays
- diffHours
- diffMinutes
- diffSeconds
- Gas Cost
- Algorithm
- Testing
- References
Version | Date | Notes |
---|---|---|
v1.00-pre-release | May 25 2018 | "Rarefaction" pre-release. I'm currently trying to get this library audited, so don't use in production mode yet. |
v1.00-pre-release-a | Jun 2 2018 | "Rarefaction" pre-release. Added the contracts/BokkyPooBahsDateTimeContract.sol wrapper for convenience |
v1.00-pre-release-b | Jun 4 2018 | "Rarefaction" pre-release. Replaced public function with internal for easier EtherScan verification - a83e13b |
TODO
User /u/_dredge asked the following questions:
Some Muslim countries have a Friday/Saturday weekend. Workday(1-7) may be more useful.
I presume leap seconds and such details are taken care of in the Unix timecode.
Some additional ideas for functions below.
Quarter calculations
weekNumber(1-53)
Complete years / months / weeks / days
Nearest years / months / weeks / days
Regarding regions and systems where Friday/Saturday are weekends, please use the function getDayOfWeek(timestamp)
that returns 1 (= Monday, ..., 7 (= Sunday) to determine whether you should treat a day as a weekday or weekend.
See the next question regarding the leap seconds.
Regarding the additional ideas, thanks!
Asked by Oleksii Matiiasevych and /u/_dredge above.
For example, a leap second was inserted on Jan 01 1999.
From the first answer to Unix time and leap seconds:
The number of seconds per day are fixed with Unix timestamps.
The Unix time number is zero at the Unix epoch, and increases by exactly 86400 per day since the epoch.
So it cannot represent leap seconds. The OS will slow down the clock to accommodate for this. The leap seconds is simply not existent as far a Unix timestamps are concerned.
And from the second answer to Unix time and leap seconds:
Unix time is easy to work with, but some timestamps are not real times, and some timestamps are not unique times.
That is, there are some duplicate timestamps representing two different seconds in time, because in unix time the sixtieth second might have to repeat itself (as there can't be a sixty-first second). Theoretically, they could also be gaps in the future because the sixtieth second doesn't have to exist, although no skipping leap seconds have been issued so far.
Rationale for unix time: it's defined so that it's easy to work with. Adding support for leap seconds to the standard libraries is very tricky. ...
This library aims to replicate the Unix time functionality and assumes that leap seconds are handled by the underlying operating system.
All dates, times and Unix timestamps are UTC.
Unit | Range | Notes |
---|---|---|
timestamp | >= 0 | Unix timestamp, number of seconds since 1970/01/01 00:00:00 UTC |
year | 1970 ... 2345 | |
month | 1 ... 12 | |
day | 1 ... 31 | |
hour | 0 ... 23 | |
minute | 0 ... 59 | |
second | 0 ... 59 | |
dayOfWeek | 1 ... 7 | 1 = Monday, ..., 7 = Sunday |
year/month/day | 1970/01/01 ... 2345/12/31 |
_days
, _months
and _years
variable names are _
-prefixed as the non-prefixed versions are reserve words in Solidity.
All functions operate on the uint
timestamp data type, except for functions prefixed with _
.
Calculate the number of days _days
from 1970/01/01 to year
/month
/day
.
function _daysFromDate(uint year, uint month, uint day) public pure returns (uint _days)
Calculate year
/month
/day
from the number of days _days
since 1970/01/01 .
function _daysToDate(uint _days) public pure returns (uint year, uint month, uint day)
Calculate the timestamp
to year
/month
/day
.
function timestampFromDate(uint year, uint month, uint day) public pure returns (uint timestamp)
Calculate the timestamp
to year
/month
/day
hour
:minute
:second
UTC.
function timestampFromDateTime(uint year, uint month, uint day, uint hour, uint minute, uint second) public pure returns (uint timestamp)
Calculate year
/month
/day
from timestamp
.
function timestampToDate(uint timestamp) public pure returns (uint year, uint month, uint day)
Calculate year
/month
/day
hour
:minute
:second
from timestamp
.
function timestampToDateTime(uint timestamp) public pure returns (uint year, uint month, uint day, uint hour, uint minute, uint second)
Is the year specified by timestamp
a leap year?
function isLeapYear(uint timestamp) public pure returns (bool leapYear)
Is the specified year
(e.g. 2018) a leap year?
function _isLeapYear(uint year) public pure returns (bool leapYear)
Is the day specified by timestamp
a weekday (Monday, ..., Friday)?
function isWeekDay(uint timestamp) public pure returns (bool weekDay)
Is the day specified by timestamp
a weekend (Saturday, Sunday)?
function isWeekEnd(uint timestamp) public pure returns (bool weekEnd)
Return the day in the month daysInMonth
for the month specified by timestamp
.
function getDaysInMonth(uint timestamp) public pure returns (uint daysInMonth)
Return the day in the month daysInMonth
(1, ..., 31) for the month specified by the year
/month
.
function _getDaysInMonth(uint year, uint month) public pure returns (uint daysInMonth)
Return the day of the week dayOfWeek
(1 = Monday, ..., 7 = Sunday) for the date specified by timestamp
.
function getDayOfWeek(uint timestamp) public pure returns (uint dayOfWeek)
Get the year
of the date specified by timestamp
.
function getYear(uint timestamp) public pure returns (uint year)
Get the month
of the date specified by timestamp
.
function getMonth(uint timestamp) public pure returns (uint month)
Get the day of the month day
(1, ..., 31) of the date specified timestamp
.
function getDay(uint timestamp) public pure returns (uint day)
Get the hour
of the date and time specified by timestamp
.
function getHour(uint timestamp) public pure returns (uint hour)
Get the minute
of the date and time specified by timestamp
.
function getMinute(uint timestamp) public pure returns (uint minute)
Get the second
of the date and time specified by timestamp
.
function getSecond(uint timestamp) public pure returns (uint second)
Add _years
years to the date and time specified by timestamp
.
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.
function addYears(uint timestamp, uint _years) public pure returns (uint newTimestamp)
Add _months
months to the date and time specified by timestamp
.
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.
function addMonths(uint timestamp, uint _months) public pure returns (uint newTimestamp)
Add _days
days to the date and time specified by timestamp
.
function addDays(uint timestamp, uint _days) public pure returns (uint newTimestamp)
Add _hours
hours to the date and time specified by timestamp
.
function addHours(uint timestamp, uint _hours) public pure returns (uint newTimestamp)
Add _minutes
minutes to the date and time specified by timestamp
.
function addMinutes(uint timestamp, uint _minutes) public pure returns (uint newTimestamp)
Add _seconds
seconds to the date and time specified by timestamp
.
function addSeconds(uint timestamp, uint _seconds) public pure returns (uint newTimestamp)
Subtract _years
years from the date and time specified by timestamp
.
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.
function subYears(uint timestamp, uint _years) public pure returns (uint newTimestamp)
Subtract _months
months from the date and time specified by timestamp
.
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.
function subMonths(uint timestamp, uint _months) public pure returns (uint newTimestamp)
Subtract _days
days from the date and time specified by timestamp
.
function subDays(uint timestamp, uint _days) public pure returns (uint newTimestamp)
Subtract _hours
hours from the date and time specified by timestamp
.
function subHours(uint timestamp, uint _hours) public pure returns (uint newTimestamp)
Subtract _minutes
minutes from the date and time specified by timestamp
.
function subMinutes(uint timestamp, uint _minutes) public pure returns (uint newTimestamp)
Subtract _seconds
seconds from the date and time specified by timestamp
.
function subSeconds(uint timestamp, uint _seconds) public pure returns (uint newTimestamp)
Calculate the number of years between the dates specified by fromTimeStamp
and toTimestamp
.
Note that this calculation is computed as getYear(toTimestamp) - getYear(fromTimestamp)
, rather that subtracting the years (since 1970/01/01) represented by both {to|from}Timestamp
.
function diffYears(uint fromTimestamp, uint toTimestamp) public pure returns (uint _years)
Calculate the number of months between the dates specified by fromTimeStamp
and toTimestamp
.
Note that this calculation is computed as getYear(toTimestamp) * 12 + getMonth(toTimestamp) - getYear(fromTimestamp) * 12 - getMonth(fromTimestamp)
, rather that subtracting the months (since 1970/01/01) represented by both {to|from}Timestamp
.
function diffMonths(uint fromTimestamp, uint toTimestamp) public pure returns (uint _months)
Calculate the number of days between the dates specified by fromTimeStamp
and toTimestamp
.
Note that this calculation is computed as (toTimestamp - fromTimestamp) / SECONDS_PER_DAY
, rather that subtracting the days (since 1970/01/01) represented by both {to|from}Timestamp
.
function diffDays(uint fromTimestamp, uint toTimestamp) public pure returns (uint _days)
Calculate the number of hours between the dates specified by fromTimeStamp
and toTimestamp
.
Note that this calculation is computed as (toTimestamp - fromTimestamp) / SECONDS_PER_HOUR
, rather that subtracting the hours (since 1970/01/01) represented by both {to|from}Timestamp
.
function diffHours(uint fromTimestamp, uint toTimestamp) public pure returns (uint _hours)
Calculate the number of minutes between the dates specified by fromTimeStamp
and toTimestamp
.
Note that this calculation is computed as (toTimestamp - fromTimestamp) / SECONDS_PER_MINUTE
, rather that subtracting the minutes (since 1970/01/01) represented by both {to|from}Timestamp
.
function diffMinutes(uint fromTimestamp, uint toTimestamp) public pure returns (uint _minutes)
Calculate the number of seconds between the dates specified by fromTimeStamp
and toTimestamp
.
Note that this calculation is computed as toTimestamp - fromTimestamp
.
function diffSeconds(uint fromTimestamp, uint toTimestamp) public pure returns (uint _seconds)
From executing the following function, the transaction gas cost is 24,693
> testDateTime.timestampToDateTime(1527120000)
[2018, 5, 24, 0, 0, 0]
> testDateTime.timestampToDateTime.estimateGas(1527120000)
24693
From Remix, the execution gas cost is 3,101 .
From executing the following function, the transaction gas cost is 25,054
> testDateTime.timestampFromDateTime(2018, 05, 24, 1, 2, 3)
1527123723
> testDateTime.timestampFromDateTime.estimateGas(2018, 05, 24, 1, 2, 3)
25054
From Remix, the execution gas cost is 2,566 .
The formulae to convert year/month/day hour:minute:second to a Unix timestamp and back use the algorithms from Converting Between Julian Dates and Gregorian Calendar Dates.
Note that these algorithms depend on negative numbers, so Solidity unsigned integers uint
are converted to signed integers int
to compute the date conversions and the results are converted back to uint
for general use.
The Fortran algorithm follows:
INTEGER FUNCTION JD (YEAR,MONTH,DAY)
C
C---COMPUTES THE JULIAN DATE (JD) GIVEN A GREGORIAN CALENDAR
C DATE (YEAR,MONTH,DAY).
C
INTEGER YEAR,MONTH,DAY,I,J,K
C
I= YEAR
J= MONTH
K= DAY
C
JD= K-32075+1461*(I+4800+(J-14)/12)/4+367*(J-2-(J-14)/12*12)
2 /12-3*((I+4900+(J-14)/12)/100)/4
C
RETURN
END
Translating this formula, and subtracting an offset (2,440,588) so 1970/01/01 is day 0:
days = day
- 32075
+ 1461 * (year + 4800 + (month - 14) / 12) / 4
+ 367 * (month - 2 - (month - 14) / 12 * 12) / 12
- 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4
- offset
The Fortran algorithm follows:
SUBROUTINE GDATE (JD, YEAR,MONTH,DAY)
C
C---COMPUTES THE GREGORIAN CALENDAR DATE (YEAR,MONTH,DAY)
C GIVEN THE JULIAN DATE (JD).
C
INTEGER JD,YEAR,MONTH,DAY,I,J,K
C
L= JD+68569
N= 4*L/146097
L= L-(146097*N+3)/4
I= 4000*(L+1)/1461001
L= L-1461*I/4+31
J= 80*L/2447
K= L-2447*J/80
L= J/11
J= J+2-12*L
I= 100*(N-49)+I+L
C
YEAR= I
MONTH= J
DAY= K
C
RETURN
END
Translating this formula and adding an offset (2,440,588) so 1970/01/01 is day 0:
int L = days + 68569 + offset
int N = 4 * L / 146097
L = L - (146097 * N + 3) / 4
year = 4000 * (L + 1) / 1461001
L = L - 1461 * year / 4 + 31
month = 80 * L / 2447
dd = L - 2447 * month / 80
L = month / 11
month = month + 2 - 12 * L
year = 100 * (N - 49) + year + L
Details of the testing environment can be found in test.
The DateTime library calculations have been tested for the date range 1970/01/01 to 2345/12/01 for periodically sampled dates.
The following functions were tested using the script test/01_test1.sh with the summary results saved in test/test1results.txt and the detailed output saved in test/test1output.txt:
- Deploy contracts/BokkyPooBahsDateTimeLibrary.sol library
- Deploy contracts/TestDateTime.sol contract
- Test
isLeapYear(...)
- Test
_isLeapYear(...)
- Test
isWeekDay(...)
- Test
isWeekEnd(...)
- Test
getDaysInMonth(...)
- Test
_getDaysInMonth(...)
- Test
getDayOfWeek(...)
- Test
get{Year|Month|Day|Hour|Minute|Second}(...)
- Test
add{Years|Months|Days|Hours|Minutes|Seconds}(...)
- Test
sub{Years|Months|Days|Hours|Minutes|Seconds}(...)
- Test
diff{Years|Months|Days|Hours|Minutes|Seconds}(...)
- For a range of Unix timestamps from 1970/01/01 to 2345/12/21
- Generate the year/month/day hour/minute/second from the Unix timestamp using
timestampToDateTime(...)
- Generate the Unix timestamp from the calculated year/month/day hour/minute/second using
timestampFromDateTime(...)
- Compare the year/month/day hour/minute/second to the JavaScript Date calculation
- Generate the year/month/day hour/minute/second from the Unix timestamp using
A copy of the webpage with the algorithm Converting Between Julian Dates and Gregorian Calendar Dates has been saved to docs/ConvertingBetweenJulianDatesAndGregorianCalendarDates.pdf as some people have had difficulty accessing this page.
(c) BokkyPooBah / Bok Consulting Pty Ltd - Jun 2 2018. GNU Lesser General Public License 3.0