My first post was on the Ghost of the Y2K, wherein I attempt to revisit the infamous Y2K problem (read and listen to a nice summary on NPR’s Science Friday) and also draw attention to the fact that the problem may not be gone yet for those who continue to use 2-digit year representation. Earlier this week, I wrote about a problem that one of my colleagues asked my help for. They were trying to convert a UNIX timestamp (BIGINT) to a SQL Server DATETIME representation. However, because the DATEADD function can only accept the INT data-type for the interval value, they were at a dead-end. After about an hour of trying what the UNIX timestamp is, I came up with two solutions solution for them – one of which I presented in the last post. You can read about it here.
When I was researching the possible solutions, a strange observation dawned on me and further research revealed that I was not the only one to notice this. This is popularly known as the Year 2038 problem – and is a very real threat to all 32-bit applications which hope to make it till 2038. Here, I attempt to explain the year 2038 problem and also attempt to come up with another alternate solution to using UNIX timestamp values in the DATEADD function.
The INT data-type
The INT data type is the primary integer data type in SQL Server. Consuming 4 bytes in storage, the INT is a 32-bit data-type with a range of -2^31 (-2,147,483,648) to 2^31-1 (2,147,483,647).
In Binary format, the ceiling value for INT (2,147,483,647) equates to 01111111111111111111111111111111 – All ones! (The string is exactly 32-bits long, and there are 31 “1’s” to be precise). Attempting to convert this number to a SQL Server DATETIME gives us the DATETIME as 2038-01-19 03:14:07.000.
DECLARE @DateInt INT SET @DateInt = 2147483647 /* Max. range of the INT data-type */ SELECT DATEADD(ss, @DateInt, '1970-01-01')
The Year 2038 problem
Now that we know what the UNIX timestamp is, and also knowing the eternal truth that time and tide wait for none, we will see how January 19, 2038, 3:14:08 (UTC) is represented in UNIX timestamp format. Because we know that UNIX timestamp is simply a uniformly incrementing, the number is obviously 2,147,483,647 + 1 = 2,147,483,648. Alternatively, based on the computation logic used in my previous post, the number comes out to be 2,147,483,648.
SELECT CAST((DATEDIFF(dd,'1970-01-01 00:00:00.000','2038-01-19 03:14:08.000') * 86400) AS BIGINT) + -- Get the number of seconds based on the number of days elapsed DATEDIFF(ss,'2038-01-19 00:00:00.000','2038-01-19 03:14:08.000') -- Add the number of seconds elapsed since midnight
There is a very important point I want to highlight by showing the conversion script above. If you observe, I had to cast the number to a BIGINT. This is because, when represented in binary format, 2,147,483,648 = 10000000000000000000000000000000.
This is a clear buffer overflow for the INT data-type (the 32nd bit in a 4 byte representation is the sign bit). For the INT data-type, this corresponds to the floor value of the range (-2,147,483,648). What this means is that at exactly January 19, 2038, 3:14:08 UTC time, the clocks using the 32-bit INT data-type would start reading: 1901-12-13 20:45:52.000
DECLARE @DateInt INT SET @DateInt = -2147483648 /* Min. range of the INT data-type */ SELECT DATEADD(ss, @DateInt, '1970-01-01')
The results could be disastrous if such a rollover happens unchecked. If your application is currently processing data 20 years ahead of time, you will start running into problems very soon.
This is the Year 2038 problem, also known as Y2K38 or Y2.038K or the UNIX Millennium Bug. In essence, what my colleague was asking was – “How can I overcome the year 2038 bug in my 32-bit application?”
The solution:
The only solution to the year 2038 problem is also hidden in the script shown above, wherein I use a CAST to a BIGINT value. We can use unsigned integer values, use BIGINT values, but the only real solution is to move towards 64-bit systems, where the wrap-around comes approximately 292 billion years from now. Computing systems would be very, very different by then.
Using BIGINT values for DATEADD – Method 02
My previous post dealing with using BIGINT values in DATEADD mentioned that there are actually two alternate implementations on how to represent the BIGINT UNIX timestamp values as SQL Server DATETIME. With the background of the year 2038 problem, I present before you method #02, which is as under. The script should be self-explanatory:
DECLARE @DateInt INT DECLARE @DateBigInt BIGINT /* Method 02 - The max. value for INT (2147483647) corresponds to '2038-01-19 03:14:07.000' */ /* If the passed value is greater than '2038-01-19 03:14:07.000', we add the */ /* difference (passedValue-2147483647) to '2038-01-19 03:14:07.000', */ /* else we use the standard difference with 1970. */ SET @DateInt = 1293840000 /* (UNIX time = 1293840000 => midnight, January 01, 2011) */ SET @DateBigInt = 2147483648 SELECT DATEADD(ss,@DateInt,'1970-01-01') AS 'IntegerDateRange', CASE WHEN (@DateBigInt-2147483647) > 0 THEN DATEADD(ss,(@DateBigInt-2147483647),'2038-01-19 03:14:07.000') ELSE DATEADD(ss,@DateBigInt, '1970-01-01 00:00:00.000') END AS 'BigIntDateRange'
Now that we have seen both methods of handling the year 2038 problem created by 32-bit data-types, it is up to the reader to pick and use any method they find best suited for their application code.
Some Trivia
Today, I leave you with some very interesting trivia (courtesy: Wikipedia):
- UNIX Time passed 1000000000 on September 9, 2001, at exactly 01:46:40 (UTC). This is popularly called the UNIX billennium
- UNIX Time passed 1234567890 on February 13, 2009, at exactly 23:31:30 (UTC) – Interestingly, it was a Friday
- UNIX Time passed 2^30 (1073741824) on January 10, 2004, at exactly 13:37:04 (UTC). This is half-way between UNIX epoch and year 2038 problem
- UNIX Time passed 1111111111 on March 18, 2005, at exactly 01:58:31 (UTC)
- UNIX Time will pass 2000000000 on May 18, 2033, at exactly 03:33:20 (UTC). This will be the 2nd UNIX billennium
In Conclusion…
In my previous post, I had mentioned that I did not believe the Microsoft Connect case filed for making DATEADD compatible with BIGINT is really a SQL Server defect. This is because as demonstrated above, the issue is actually an architectural limitation of the 32-bit architecture. Whatever code change we do to overcome this limitation will always be workaround, and not the true solution.
Many embedded systems (like automobile control systems and CNC controllers) use the UNIX timestamp to maintain their internal clock. These systems will also be affected by the Year 2038 problem. Hence, my question to the community is do you work with UNIX timestamps? If yes, I would like to hear from you as to how you handle (or plan to handle) both these issues that we discussed this week. Do leave a comment as you go.
Be courteous. Drive responsibly.
It seems to me the best solution is to use bigints with this expression: `DATEADD(dd, @DateBigInt / 86400, DATEADD(ss, @DateBigInt % 86400, ‘1970-01-01’))`
– Erik Eckhardt
LikeLike