Проблемът от 2038 година (наричан още грешка Y2K38) се отнася до проблем, който някои компютърни системи могат да срещнат, когато се справят с времена от 19.01.2038 г. 03:14:07.
Много компютърни системи, като Unix и Unix-базирани системи, не изчисляват времето с помощта на григорианския календар. Те изчисляват времето като брой секунди от 1 януари 1970 г. Следователно в тези системи времето се представя като голямо число (т.е. броят на секундите, изминали от 1970-01-01 00:00:00). Това обикновено се нарича Epoch time, Unix time, Unix Epoch time или POSIX time. Докато пиша това, времето за Unix е 1560913841. И докато пиша този следващ ред, времето на Unix се е увеличило до 1560913879.
Проблемът 2038 е причинен от факта, че много системи съхраняват това число като подписано 32-битово двоично цяло число. Обхватът на 32-битово цяло число със знак е -2,147,483,648 до 2,147,483,647. Това означава, че последното време за Епоха, което може да бъде представено, е 2147483647. Това ще се случи в 03:14:07 във вторник, 19 януари 2038 г.
След това резултатът до голяма степен ще зависи от системата. В много системи ще се случи препълване на цяло число и всички по-късни моменти ще се обвият и ще бъдат съхранени вътрешно като отрицателно число. Резултатът е, че една секунда по-късно времето ще бъде интерпретирано като на 13 декември 1901 г., а не на 19 януари 2038 г.
Въпреки това, можете също да получите различни резултати, в зависимост от приложението, което се използва. Дори ако вашата операционна система няма проблем, вашият собствен код все още може да има проблем. Например, ако сте написали персонализиран код за връщане на Unix време и го съхранявате в подписано 4 байтово цяло число, ще имате проблеми. В такива случаи пренаписването на кода за използване на 8 байтово цяло число може да е всичко, което трябва да направите.
Тъй като този уебсайт е свързан изцяло с бази данни, ето някои примери за бази данни.
Пример 1 – MySQL
В MySQL, TIMESTAMP
типът данни поддържа дати/часове от „1970-01-01 00:00:01.000000“ UTC до „2038-01-19 03:14:07.999999“. Следователно можете да кажете, че всяка база данни, използваща този тип данни, има грешка Y2K38.
MySQL също има вградена функция, наречена UNIX_TIMESTAMP()
което, както може да очаквате, връща времевата марка на Unix.
UNIX_TIMESTAMP()
функцията приема незадължителен аргумент, който ви позволява да посочите дата, която да използвате за времето на Unix (т.е. броят на секундите от ‘1970-01-01 00:00:00’ UTC до часа, който сте посочили). Валидният диапазон от стойности на аргументите е същият като за TIMESTAMP
тип данни, който е „1970-01-01 00:00:01.000000“ UTC до „2038-01-19 03:14:07.999999“ UTC. Ако подадете дата извън диапазона на тази функция, тя връща 0
.
Ето какво се случва, ако се опитате да използвате тази функция, за да върнете времето на Unix от дата след „2038-01-19 03:14:07.999999“:
ИЗБЕРЕТЕ UNIX_TIMESTAMP('2038-01-20') Резултат;
Резултат:
<пред>+--------+| Резултат |+--------+| 0 |+-------+
Получаваме 0
защото аргументът за дата е извън поддържания диапазон.
Свързан доклад за грешка беше повдигнат за екипа на MySQL през 2005 г. (въпреки че някои от спецификите изглежда са различни) и към момента на писането все още не е разгледан.
Подобен проблем също беше повдигнат за справяне с ограниченията с TIMESTAMP
тип данни, който също предстои да бъде разгледан.
Пример 2 – SQL Server
SQL Server в момента няма еквивалент на UNIX_TIMESTAMP
на MySQL функция. Следователно, ако трябва да върнете Epoch time, ще трябва да направите нещо подобно:
ИЗБЕРЕТЕ DATEDIFF(SECOND,'1970-01-01', GETUTCDATE());
Това е добре за дати преди проблема от 2038 г. След тази дата ще имате проблеми, тъй като DATEDIFF()
функцията връща резултата като int тип данни. Винт типът данни има диапазон от -2^31 (-2,147,483,648) до 2^31-1 (2,147,483,647).
Ето какво се случва, ако се опитам да върна времето за епоха по-късно от „2038-01-19 03:14:07“:
ИЗБЕРЕТЕ DATEDIFF(SECOND,'1970-01-01', '2038-01-19 03:14:08') КАТО 'Резултат';
Резултат:
Функцията datediff доведе до препълване. Броят на частите от дата, разделящи два екземпляра на дата/час, е твърде голям. Опитайте се да използвате datediff с по-малко точна част от датата.
За щастие има и DATEDIFF_BIG()
функция, която прави абсолютно същото нещо, с изключение на това, че връща резултата като bigint тип данни.
Така че можем да пренапишем предишния пример на следното, за да преодолеем този проблем:
ИЗБЕРЕТЕ DATEDIFF_BIG(SECOND,'1970-01-01 00:00:00', '2038-01-19 03:14:08') КАТО 'Резултат';
Резултат:
+-----------+| Резултат ||-----------|| 2147483648 |+-----------+
Най-големият типът данни използва 8 байта (за разлика от 4 байта за int ), така че ще трябва да решите дали да превключите към DATEDIFF_BIG()
сега или по-късно. Ако приложението ви се занимава с бъдещи дати, може да е разумно да го направите по-рано от по-късно.