09.11.2006, 17:22 | #1 |
Участник
|
Функция, вычисляющая разницу между датами.
Есть 2 даты на входе. Нужно выводить разницу между ними в формате столько то лет, столько то месяцев, столько то дней. Никто не поделится? А то самому нет времени писать. Спасибо.
|
|
09.11.2006, 17:30 | #2 |
Участник
|
container dateDiff(date d1, date d2)
{ return [Global::yearDiff(d1,d2), Global::MthDiff(d1,d2), d2 - d1]; } |
|
09.11.2006, 18:06 | #3 |
Участник
|
Цитата:
X++: date d1 = str2date("1996.12.21", 321); date d2 = str2date("1994.11.18", 321); ; info(strfmt("distance is %1 year(s), %2 month(s) and %3 day(s)", intvNo(d1, d2, IntvScale::Year), intvNo(d1, d2, IntvScale::Month), intvNo(d1, d2, IntvScale::Day))); |
|
09.11.2006, 18:53 | #4 |
Участник
|
08.12.2006 - 09.11.2006
Результат: distance is 0 year(s), 1 month(s) and -1 day(s) С уважением, itfs. |
|
09.11.2006, 19:18 | #5 |
Участник
|
Нашел таки, вот 2 функции из одного класса:
X++: static str MydateDiff(transDate _d1, transDate _d2, boolean _include = False) { str _s; int M_year, m_mth, m_days; ; M_year = Global::yearDiff(_d2,_d1); M_mth = mthofYr(_d2) - mthofYr(_d1); if (M_mth < 0) { M_mth+=12; } M_days = DayOfmth(_d2) - DayOfmth(_d1); if (M_days < 0) { M_mth--; M_days += DayOfmth(dateEndMth(_d1)); } if (_include) M_days++; _s = strfmt("%1 %2 %3", PeriodInterval::PeriodSklInt(IntvScale::Year, M_year), PeriodInterval::PeriodSklInt(IntvScale::Month, M_mth), PeriodInterval::PeriodSklInt(IntvScale::Day, M_days) ); Return _s; } X++: static Str PeriodSklInt(IntvScale: _Period, int _Count) { // возвращает строку "кол-во периодов" str _s; ; if (_Count) { if (_Count == 1) { switch (_Period) { case IntvScale::Day : _s = "день"; break; case IntvScale::Week : _s = "неделя"; break; case IntvScale::Month : _s = "месяц"; break; case IntvScale::Quarter : _s = "квартал"; break; case IntvScale:Year : _s = "год"; break; default : _s = "период"; break; } } else if ((_Count <= 4)) { switch (_Period) { case IntvScale::Day : _s = "дня"; break; case IntvScale::Week : _s = "недели"; break; case IntvScale::Month : _s = "месяца"; break; case IntvScale::Quarter : _s = "квартала"; break; case IntvScale::Year : _s = "года"; break; default : _s = "периода"; break; } } else { switch (_Period) { case IntvScale::Day : _s = "дней"; break; case IntvScale::Week : _s = "недель"; break; case IntvScale::Month : _s = "месяцев"; break; case IntvScale::Quarter : _s = "кварталов"; break; case IntvScale::Year : _s = "лет"; break; default : _s = "периодов"; break; } } Return strfmt("%1 %2",_Count, _s); } else Return ""; } } |
|
|
За это сообщение автора поблагодарили: gl00mie (1). |
09.11.2006, 19:46 | #6 |
Участник
|
Цитата:
2. Но хотел бы чуть-чуть добавить: Вместо str2date(...) можно писать литерал 21\12\1996 |
|
10.11.2006, 08:02 | #7 |
Участник
|
Цитата:
Сообщение от gl00mie
Писать ничего не надо, есть стандартные функции ядра по работе с интервалами дат (см., например, тут, раздел Interval):
X++: date d1 = str2date("1996.12.21", 321); date d2 = str2date("1994.11.18", 321); ; info(strfmt("distance is %1 year(s), %2 month(s) and %3 day(s)", intvNo(d1, d2, IntvScale::Year), intvNo(d1, d2, IntvScale::Month), intvNo(d1, d2, IntvScale::Day))); |
|
10.11.2006, 08:44 | #8 |
Участник
|
RHRMDateDiff
Можете еще посмотреть класс RHRMDateDiff:
RHRMDateDiff rhrmDateDiff = new RHRMDateDiff( 21\01\2006, 12\03\2007 ) ; info( strfmt( "%1, %2, %3", rhrmDateDiff.getYears(), rhrmDateDiff.getMonths(), rhrmDateDiff.getDays() ) ) ; Результат : 1, 1, 22 Последний раз редактировалось petergunn; 10.11.2006 в 08:51. |
|
10.11.2006, 09:31 | #9 |
Moderator
|
Увы, не всё там гладко в RHRMDateDiff...
Следующий джоб: X++: static void KKu_Job_6B10_TestDateDiff(Args _args) { void getDateDiff(date _dateBeg, date _dateEnd) { RHRMDateDiff dd; dd = new RHRMDateDiff(_dateBeg, _dateEnd); info(strfmt("distance is %1 year(s), %2 month(s) and %3 day(s)", dd.getYears(), dd.getMonths(), dd.getDays() )); } getDateDiff(31\01\2005, 29\04\2006); getDateDiff(31\01\2005, 30\04\2006); getDateDiff(31\01\2005, 01\05\2006); info('---------------------------'); getDateDiff(31\01\2004, 27\02\2004); getDateDiff(31\01\2004, 28\02\2004); getDateDiff(31\01\2004, 29\02\2004); getDateDiff(31\01\2004, 01\03\2004); } Код: distance is 1 year(s), 2 month(s) and 29 day(s) distance is 1 year(s), 2 month(s) and 30 day(s) distance is 1 year(s), 3 month(s) and 1 day(s) --------------------------- distance is 0 year(s), 0 month(s) and 27 day(s) distance is 0 year(s), 0 month(s) and 28 day(s) distance is 0 year(s), 0 month(s) and 29 day(s) distance is 0 year(s), 1 month(s) and 1 day(s) |
|
10.11.2006, 12:09 | #10 |
Участник
|
Цитата:
X++: // возвращает строку "кол-во периодов" static str PeriodSklInt(IntvScale _period, int _count) { str _s; ; if (!_count) return ""; if (_count < 0) _count = -_count; if ((_count mod 100<10 || _count mod 100>20) && _count mod 10==1) switch (_period) { case IntvScale::Day : _s = "день"; break; case IntvScale::Week : _s = "неделя"; break; case IntvScale::Month : _s = "месяц"; break; case IntvScale::Quarter : _s = "квартал"; break; case IntvScale::Year : _s = "год"; break; default : _s = "период"; break; } else if ((_count mod 100<10 || _count mod 100>20) && _count mod 10>1 && _count mod 10<=4) switch (_period) { case IntvScale::Day : _s = "дня"; break; case IntvScale::Week : _s = "недели"; break; case IntvScale::Month : _s = "месяца"; break; case IntvScale::Quarter : _s = "квартала"; break; case IntvScale::Year : _s = "года"; break; default : _s = "периода"; break; } else switch (_period) { case IntvScale::Day : _s = "дней"; break; case IntvScale::Week : _s = "недель"; break; case IntvScale::Month : _s = "месяцев"; break; case IntvScale::Quarter : _s = "кварталов"; break; case IntvScale::Year : _s = "лет"; break; default : _s = "периодов"; break; } return strfmt("%1 %2",_count, _s); } X++: static str myDateDiff(date _d1, date _d2, boolean _include = false) { date m_d1, m_d2; // m_d2 >= m_d1 str _s; int m_year, m_mth, m_days; ; if(_d1<_d2) { m_d1 = _d1; m_d2 = _d2; } else { m_d1 = _d2; m_d2 = _d1; } m_year = yearDiff(m_d2,m_d1); m_mth = mthOfYr(m_d2) - mthOfYr(m_d1); if (m_mth < 0) m_mth += 12; m_days = dayOfMth(m_d2) - dayOfMth(m_d1); if (m_days < 0) { m_mth--; m_days += dayOfMth(dateEndMth(m_d1)); } if (m_mth < 0) m_mth += 12; if (_include) m_days++; if (m_days == dayOfMth(dateEndMth(m_d2))) { m_mth++; m_days = 0; } _s = strltrim(strrtrim(strfmt("%1 %2 %3", PeriodSklInt(IntvScale::Year, m_year), PeriodSklInt(IntvScale::Month, m_mth), PeriodSklInt(IntvScale::Day, m_days)))); return _s; } X++: info("distance is " + myDateDiff(10\09\2005, 01\10\2006)); info("distance is " + myDateDiff(10\10\2005, 09\10\2006)); info("distance is " + myDateDiff(31\01\2005, 29\04\2006)); info("distance is " + myDateDiff(31\01\2005, 30\04\2006)); info("distance is " + myDateDiff(31\01\2005, 01\05\2006)); info('---------------------------'); info("distance is " + myDateDiff(31\01\2004, 27\02\2004)); info("distance is " + myDateDiff(31\01\2004, 28\02\2004)); info("distance is " + myDateDiff(31\01\2004, 29\02\2004)); info("distance is " + myDateDiff(31\01\2004, 01\03\2004)); PHP код:
Последний раз редактировалось gl00mie; 10.11.2006 в 12:20. |
|
|
За это сообщение автора поблагодарили: itfs (1). |
10.11.2006, 17:38 | #11 |
Moderator
|
Есть ли в Axapta функция DateAdd ?
Даа... Насколько компактнее и прозрачнее получается алгоритм на VBA при использовании функции DateAdd:
Код: Sub VBA_TestDifferenceBetweenTwoDates() Dim dateBeg As Date, dateEnd As Date, dateTest As Date Dim y1 As Integer, m1 As Integer, y2 As Integer, m2 As Integer Dim ys As Integer, ms As Integer, ds As Integer dateBeg = DateSerial(2006, 1, 31) dateEnd = DateSerial(2006, 4, 30) y1 = Year(dateBeg): m1 = Month(dateBeg) y2 = Year(dateEnd): m2 = Month(dateEnd) ms = (y2 * 12 + m2) - (y1 * 12 + m1) 'предварительная разница в месяцах dateTest = DateAdd("m", ms, dateBeg) 'пробная дата на ms месяцев от начала ds = DateDiff("d", dateTest, dateEnd) 'предварит-ная разница в днях 'в пределах последнего месяца (+/-) If ds < 0 Then 'ЕСЛИ предварит-но "перебрали" с целыми месяцами от начала ms = ms - 1 'ТО окончат-ная разница в месяцах dateTest = DateAdd("m", ms, dateBeg) 'новая "пробная" дата ds = DateDiff("d", dateTest, dateEnd) 'окончательная разница в днях (+) End If ys = ms \ 12: ms = ms Mod 12 'годы - тривиально от месяцев Debug.Print ys, ms, ds '<- Годы, Месяцы, Дни разницы соответственно 'или еще и недели внутри месяца :-) Debug.Print ys, ms, ds \ 7, ds Mod 7 '<- Годы, Месяцы, Недели, Дни разницы End Sub А вот есть ли в Аксе приемлемый аналог функции DateAdd ? P.S. 30.01.07 Как выяснилось здесь, для месяцев - есть. В Global, называется dateMthFwd. |
|
11.11.2006, 07:59 | #12 |
Участник
|
Порылся в стандарте - есть такие фишки - например из печатной формы Т2
workDate = str2date(emplTable.SeniorityDate_RU(false, ""), 123); if (workDate) { dateDiff = new RHRMDateDiff(workDate, Today()); if (dateDiff.getValidate()) { if (dateDiff.getDays() > 0) { worddocument.insertFieldValue("ET_SenDay",dateDiff.getDays()); } if (dateDiff.getMonths() > 0) { worddocument.insertFieldValue("ET_SenMth",dateDiff.getMonths()); } if (dateDiff.getYears() > 0) { worddocument.insertFieldValue("ET_SenYr",dateDiff.getYears()); } } } |
|
11.11.2006, 18:49 | #13 |
Moderator
|
Еще одна разность между двумя датами в годах, месяцах, днях (и не только)
Может быть и есть где-то глубоко в Аксапте функция а-ля 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) ); } } } Ну и раз появилась такая функция, значит появилась и возможность свой вчерашний алгоритм воплотить в 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))); } 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). |
02.03.2012, 13:49 | #14 |
Moderator
|
Формулки для тёти бухгалтера (кадровика) в Excel
Пришлось тут наваять версию алгоритма для Excel на уровне формул в соседних колонках. Делал по мотивам своей вышеприведенной процедурки VBA_TestDifferenceBetweenTwoDates.
Произошло очередное укрепление убеждения в том, что разность между двумя датами - это кол-во полных месяцев + дополнительные дни (сверх полных месяцев). Всё остальное - полные годы, доп.месяцы (сверх полных лет), недели, кварталы и что еще заблагорассудится - можно получить простыми операциями деления или получения остатка от деления. Завязываю узелок на память: в виде файла и в виде формул (для русского Excel). В виде формул - для большей наглядности (не надо лезть в файл), а также для гостей форума, для которых файл может быть недоступен. A1: Дата 1 B1: Дата 2 C1: Полн.Месяцы предв. D1: Полн.Месяцы оконч. E1: Полн.Годы F1: Месяцы сверх полн.лет G1: Дни сверх полн. мес. A2: 23.09.1984 B2: 15.12.2011 C2: =(ГОД(B2)-ГОД(A2))*12+МЕСЯЦ(B2)-МЕСЯЦ(A2) D2: =C2+ЕСЛИ(B2-ДАТАМЕС(A2;C2)<0; ЕСЛИ(B2>A2;-1; 0); ЕСЛИ(B2>A2; 0; 1)) E2: =ОТБР(D2/12) F2: =D2-E2*12 G2: =B2-ДАТАМЕС(A2;D2) Разность (Дата 1 - Дата 2) может быть с любым знаком, формулы корректно считают и в плюс, и в минус. P.S.05.03.2012. Ну что ж, как часто бывает в подобных случаях, когда надо "быстро-быстро" и "вчера", после внедрения начинаешь рассуждать спокойно, литературку почитаешь, по Сети поползаешь и поймешь, что существует (и давно) решение гораздо более изящное. Так и в этот раз - нашлась в Excel суперподходящая под задачу функция РАЗНДАТ (в англ. DATEDIF; не путать с ф-цией DateDiff VBA - при похожем названии возможности несколько различаются). Функция РАЗНДАТ является недокументированной, поэтому не появляется в списках выбора, отсутствует в справке и существует в Excel в основном для совместимости с Лотусом 1-2-3. Функция РАЗНДАТ идеальна для вычисления возраста (количества полных лет), поскольку абсолютна точна (в отличие от встречающихся в И-нете предложений делить разницу в днях на 365 или 365.25), а также для вычисления стажа (годы, месяцы, дни). Почитать про функцию можно, например, здесь: http://www.excel2003.ru/vichislenie-...ili-staja.html: Цитата:
Синтаксис функции следующий:
РАЗНДАТ(начальная_дата; конечная_дата; способ_измерения) Самый интересный аргумент, конечно, последний. Он определяет, каким именно образом и в каких единицах будет измеряться интервал между начальной и конечной датами. Этот параметр может принимать следующие значения: "y" разница в полных годах "m" в полных месяцах "d" в полных днях "yd" разница в днях с начала года без учета лет "md" разница в днях без учета месяцев и лет "ym" разница в полных месяцах без учета лет "yd" - количество дней сверх полных лет (от 0 до 365) "md" - количество дней сверх полных месяцев (от 0 до 30) "ym" - количество месяцев сверх полных лет (от 0 до 11) Таким образом, полный интервал между двумя датами может быть представлен одним из следующих вариантов: * количество дней РАЗНДАТ(дата1,дата2,"d") (или просто разница дат без использования РАЗНДАТ: дата2 - дата1) * кол-во полных лет РАЗНДАТ(дата1,дата2,"y") + кол-во дней сверх полных лет РАЗНДАТ(дата1,дата2,"yd") * кол-во полных месяцев РАЗНДАТ(дата1,дата2," m") + кол-во дней сверх полных месяцев РАЗНДАТ(дата1,дата2,"md") * случай расчета стажа: кол-во полных лет РАЗНДАТ(дата1,дата2,"y") + кол-во полных месяцев сверх полных лет РАЗНДАТ(дата1,дата2,"ym") + кол-во дней сверх полных месяцев РАЗНДАТ(дата1,дата2,"md") Да, и конечная дата должна быть больше начальной даты, т.е. в минус функция РАЗНДАТ автоматически не считает (в этом случае следует поменять даты местами). Добавил в прилагаемый файл формулы с РАЗНДАТ. Последний раз редактировалось Gustav; 05.03.2012 в 10:32. |
|