Алгоритм конвертации даты в "абсолютный" день и обратно

Рейтинг: 0Ответов: 1Опубликовано: 21.06.2023

Моей целью является вычислить количество дней между 0000.00.00 00:00:00, и до указанной даты, а потом сделать обратное преобразование.
Месяц и день я указал в "режиме индекса", поэтому они "00"; что же касается года, то соглашения с ним мне не ведомы.

Тривиально конвертировать между порядковым днём в году, и месяцем/днём, поэтому проблема сводится только к году/порядковому дню.

Посчитать количество дней – пустяковая проблема, и решить это было почти не трудно:

isLeap = (yr % 4==0) and (yr % 100!=0) or (yr % 400==0)
result = (yr*365 + yr//4 - yr//100 + yr//400 + int(yr>0)*(not isLeap)) + dy + 1

Но вот преобразовать всё обратно выбило меня из колеи.
Несколько дней головной боли, отладки с тестами, и всё равно не решается.

Например:
Год с индексом 207 561 и порядковый день с индексом 137 — это 75 810 237 дней.
Это число включает в себя корректировку на високосные года; что одновременно и является проблемой для обратного преобразования.

Моих интеллектуальных способностей хватило только чтобы вычислить индекс года от количества дней, а именно:

num = num - 1
num = num - num//146097
num = num - num//-36525
num = num - num//1461
result = num//365

Как я могу вычислить порядковый день в году, зная количество дней?

Я пытался решить это самостоятельно, и у меня даже получилось... да только вот по неведомым мне причинам после 36 600 года мой способ ломается, и каждые 100 лет год выдаётся на один меньше чем нужно.
Вот моё самое близкое решение:

type
 HDivModResult=record
  year,day:Int64;
 end;
function AbsoluteDaysToYearLinearDivMod(num:Int64):HDivModResult;
  var
   tmp:Int64;
   tgl:Boolean;
 begin
  Result.year:=0;
  Result.day:=0;
  if num<1 then
   Exit;
  num:=num-1;
  tmp:=num-(num div 146097);
  tmp:=tmp-(tmp div -36525);
  tmp:=tmp-(tmp div 1461);
  Result.year:=tmp div 365;
  tgl:=(Result.year mod 4=0)and(Result.year mod 100<>0)or(Result.year mod 400=0);
  tmp:=((num+Byte(tgl))-(Result.year div 4-Result.year div 100+Result.year div 400));
  Result.day:=tmp mod 365;
  if (tgl)and(Result.day=0) then
   begin
    tmp:=tmp div 365;
    Result.day:=365*Byte(tmp<>Result.year);
   end;
 end;

Что я понимаю не так?

Ответы

▲ 4Принят

Юлианская дата вам в помощь. Или книжица "Алгоритмы. Просто как 2x2" — там даже код есть. Все советы "идти по одному дню" с учетом всех високосных и длин месяцев дадут только очень запутанный код.

Вот эти волшебные функции по переводу "туда-сюда" для григорианского календаря.

По-хорошему, надо учитывать, что до 1918 года календарь у нас был юлианский (то, что мы называем старый стиль). Функции даны уже для нового.

Так что функции применимы для Европы (и то не всей...) с 1529 года примерно.

Код на С++, но, думаю, разберетесь.

long julianDate(int y, int m, int d)
{
    if (m <= 2) {
        y--;
        m += 12;
    };
    long A = y/100;
    A = 2 - A + A/4;
    long J = (1461L * y)/ 4;
    long K = (306001L*(m + 1))/10000L;
    return J + K + d + 1720995L + A;
};

void grigorianDate(long JD,
                   int& y,  int& m, int&  d)
{
    long A = (JD*4 - 7468865L)/146097L;
    A = JD + 1 + A - (A/4L);
    long B = A + 1524;
    long C = (B*20L - 2442L)/7305L;
    long D = (C * 1461L) / 4L;
    long E = (10000L * (B-D)) / 306001L;
    d = B - D - E*306001L/10000L;
    m = ( E <= 13 ) ? E - 1 : E - 13;
    y = ( m > 2 ) ? C - 4716 : C - 4715;
};

int weekday(long jd) { return (jd+1)%7; }
// 0 - воскресенье, 1 - понедельник и т.д.

Только я бы так далеко, на тысячи лет, не заглядывал... Там даже григорианский календарь уже начнет давать сбой, так что если человечество и останется живо, то календарь уж точно поменяет.