Показать сообщение отдельно
Старый 11.11.2006, 18:49   #13  
Gustav is offline
Gustav
Moderator
Аватар для Gustav
SAP
Лучший по профессии 2009
 
1,858 / 1152 (42) ++++++++
Регистрация: 24.01.2006
Адрес: Санкт-Петербург
Записей в блоге: 19
Еще одна разность между двумя датами в годах, месяцах, днях (и не только)
Цитата:
Сообщение от Gustav Посмотреть сообщение
А вот есть ли в Аксе приемлемый аналог функции DateAdd ?
Может быть и есть где-то глубоко в Аксапте функция а-ля DateAdd, но я уже сваял свою, предназначенную только для интервала типа "месяц" (а в предложенном вчера VBA-шном алгоритме больше и не требуется - да и хорошо). Моя маленькая функция DateAddMonths корректно обрабатывает концовки месяцев, так же как и фирменные DateAdd из VBA и SQL Server.
Следующий джоб представляет эту функцию и небольшой тест для нее:
X++:
static void KKu_Job_6B11_TestDateAddMonths(Args _args)
{
    int d, m;
    date dam;
 
    // --------------------------------------------------------------------------------------
    // Returns a date containing a date to which a specified number of months has been added;
    // _months - number of months you want to add, it can be positive (to get dates in the future) or negative (to get dates in the past);
    // _date - date to which the months are added;
    date DateAddMonths(int _months, date _date)
    {
        int y2, m2, d2, ms;
        ms = ( year(_date) * 12 + mthOfYr(_date) ) + _months;
        y2 = (ms-1) div 12;
        m2 = ((ms-1) mod 12) + 1;
        d2 = min( dayOfMth(_date), dayOfMth( endMth( mkDate(1, m2, y2))) );
        return mkDate(d2, m2, y2);
    }
    // --------------------------------------------------------------------------------------
    ;
    
    // тестирование функции DateAddMonths на "критических" днях
    for (d=28; d<= 31; d++) // последние дни месяца (март 2004)
    {
        for (m=-12; m<= 12; m++) // плюс-минус год помесячно
        {
            dam = DateAddMonths( m, mkDate(d,3,2004));
            if (d != dayOfMth(dam)) // для пущей наглядности выводим в инфолог только те даты, в которых значения дней не совпадают
                info( strfmt('от даты %1.03.04 отстоит ровно на %2 месяц. дата %3', d, m, dam) );
        }
    }
}
(пижонские комментарии на английском творчески спионерены из хелпа по VBA)

Ну и раз появилась такая функция, значит появилась и возможность свой вчерашний алгоритм воплотить в X++. Что я и делаю, теперь уже полноценно присоединяясь своим "пятачком" к вкладу других коллег по ветке. Нижеследующий джоб использует рассмотренную выше функцию DateAddMonths для нужд другой функции - DateDiffYMDWD, которая для заданных начальной и конечной даты возвращает контейнер из пяти значений ("Year,Month,Day,Week,Day"), характеризующих разность между этими датами (см. комментарии в коде):
X++:
static void KKu_Job_6B11_TestDateDiff_2(Args _args)
{
    container c;
     
    date DateAddMonths(int _months, date _date)
    {
        int y2, m2, d2, ms;
        ms = ( year(_date) * 12 + mthOfYr(_date) ) + _months;
        y2 = (ms-1) div 12;
        m2 = ((ms-1) mod 12) + 1;
        d2 = min( dayOfMth(_date), dayOfMth( endMth( mkDate(1, m2, y2))) );
        return mkDate(d2, m2, y2);
    }
     
    //------------------------------------------------------------------------------------------
    // разность между двумя датами в годах, месяцах, днях/неделях-днях
    // если конечная дата меньше начальной, то вычисляется разность назад (-)
    container DateDiffYMDWD(date _dateBeg, date _dateEnd)
    {
        date dtBeg, dtEnd, dtTest;
        int ms, ds, sgn;
    
        if (_dateBeg <= _dateEnd) 
        {
            dtBeg = _dateBeg; dtEnd = _dateEnd; sgn = 1;
        }
        else // даты - наоборот, т.е. будет вычислена разность назад
        {
            dtBeg = _dateEnd; dtEnd = _dateBeg; sgn = -1;
        }
    
        ms = ( year(dtEnd) * 12 + mthOfYr(dtEnd) )
           - ( year(dtBeg) * 12 + mthOfYr(dtBeg) ); // предварительная разница в месяцах
    
        dtTest = DateAddMonths(ms, dtBeg); // пробная дата на ms месяцев вперёд от начала
        ds = dtEnd - dtTest; // предварит-ная разница в днях в пределах последнего месяца (+/-)
    
        if (ds < 0) //ЕСЛИ предварит-но "перебрали" с полными месяцами от начала
        {
            ms = ms - 1;                 //ТО окончат-ная разница в месяцах
            dtTest = DateAddMonths(ms, dtBeg);  // новая "пробная" дата
            ds = dtEnd - dtTest;    //окончательная разница в днях (+)
        }
    
        return [(sgn*ms) div 12,   // полные годы разницы - от 0 до бесконечности
                (sgn*ms) mod 12,   // полные месяцы (свыше полных лет) - от 0 до 11
                (sgn*ds)       ,   // дни неполного месяца (свыше полных месяцев) - от 0 до 30
                (sgn*ds) div 7 ,   // недели месяца (свыше полных месяцев) - от 0 до 4
                (sgn*ds) mod 7 ,   // дни неполной недели (свыше полных недель) - от 0 до 6
                (sgn*(dtEnd - dtBeg)), // общей кол-во дней разности - от от 0 до бесконечности
                (sgn*(dtEnd - DateAddMonths(ms - (ms mod 12), dtBeg)) ) // дни неполного года - от 0 до 365
               ];
    }
    //------------------------------------------------------------------------------------------
      
    c = DateDiffYMDWD( mkDate(13,11,2006), mkDate(1,1,2005) );
    info( strfmt('Difference between %1 and %2 :', mkDate(13,11,2006), mkDate(1,1,2005)));
    info( strfmt('years: %1 , months: %2 , daysOfMonth: %3 , weeksOfMonth: %4 , daysOfWeek: %5, allDays: %6, daysOfYear: %7',
                  conpeek(c,1), conpeek(c,2), conpeek(c,3), conpeek(c,4), conpeek(c,5), conpeek(c,6), conpeek(c,7)));
    info('---------------------------------------------------');
    c = DateDiffYMDWD( mkDate(13,11,2006), mkDate(1,1,2008) );
    info( strfmt('Difference between %1 and %2 :', mkDate(13,11,2006), mkDate(1,1,2008)));
    info( strfmt('years: %1 , months: %2 , daysOfMonth: %3 , weeksOfMonth: %4 , daysOfWeek: %5, allDays: %6, daysOfYear: %7',
                  conpeek(c,1), conpeek(c,2), conpeek(c,3), conpeek(c,4), conpeek(c,5), conpeek(c,6), conpeek(c,7)));
}
В общем, тему для себя удовлетворенно закрыл. Даже в двух "экземплярах": на X++ и на VBA

P.S. 13.11.06 - подправил функцию DateDiffYMDWD - теперь если конечная дата меньше начальной (_dateEnd < _dateBeg), то значения разности в возвращаемом контейнере будут со знаком "-" (т.е. разность "в прошлое"). А также добавил 6-й и 7-й элемент в контейнер.

В итоге, имея такой контейнер, можно далее легко получать такие разницы между двумя датами, как:

1. в годах, месяцах, днях;
2. в годах, месяцах, неделях, днях;
3. в годах, днях;
4. в днях;

Вариант 1 актуален для HR-менеджмента при подсчете трудового стажа сотрудника.
Варианты 2-4 имеют "чисто теоретический характер", хотя всякое может быть...

Не показаны некоторые другие возможные варианты, которые тривиально могут быть получены из имеющихся в контейнере значений:
например,
общий срок в полных месяцах = годы * 12 + месяцы (где "годы" - 1-е значение в контейнере, "месяцы" - 2-е),
или
общий срок в полных неделях = дни div 7 (где "дни" - 6-е значение в контейнере).
За это сообщение автора поблагодарили: gl00mie (2).