Дата окончания расчета месяца в 4GL

Эта процедура возвращается 31.12.2016 вместо 31.12.2015 и портит отчет. Есть идеи, где все идет не так?

    LET date_month = MONTH(p_selection.date_from)
IF  date_month = 12 THEN 
    LET date_month = 1
    LET p_selection.date_from = p_selection.date_from + 1 UNITS YEAR
    LET date_thru = date_month,"/01/",YEAR(p_selection.date_from)
    LET p_selection.date_from = p_selection.date_from - 1 UNITS YEAR
ELSE 
    LET date_month = date_month + 1
    LET date_thru = date_month,"/01/",YEAR(p_selection.date_from)
END IF

LET p_selection.date_thru = date_thru CLIPPED

IF YEAR(p_selection.date_thru) <> YEAR(p_selection.date_from) THEN 
    LET p_selection.date_thru = p_selection.date_thru + 1 UNITS YEAR
END IF 
    LET p_selection.date_thru = p_selection.date_thru - 1

3 ответа

Предполагая ввод p_selection.date_from это 01.12.2015 ...

IF date_month = 12 возвращает TRUE, поэтому date_thru рассчитывается как 01.01.2016

Но потом второй IF Заявление также возвращает TRUE, добавив еще один год к p_selection.date_thru (01.01.2017), перед тем как уменьшить его на один день до 31.12.2016.

Мне кажется, что кто-то несколько раз пытался вычислить дату последнего дня месяца, и требуется тот или иной метод, а не оба. Тот, который появляется первым в вашем коде, особенно сомнителен - есть предположение, что DBDATE это американский формат, когда он выполняется между DATE и CHAR, что совершенно не нужно.

Гораздо более простым решением было бы просто вычислить:

LET p_selection.date_thru = 
    MDY(MONTH(p_selection.date_from), 1, YEAR(p_selection.date_from))
    + 1 UNITS MONTH - 1 UNITS DAY

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

Вот некоторый старый код, который некоторое время не видел свет, но все еще действителен I4GL.

ltmonth.4gl

{
    @(#)$Id: ltmonth.4gl,v 1.4 1990/04/05 11:02:05 john Exp $
    @(#)Sphinx Informix Tools: General Library
    @(#)Find last day of this month
    @(#)Author: JL
}

FUNCTION last_of_this_month(edate)

    DEFINE
        edate   DATE        { Effective date (frequently TODAY) }

    IF edate IS NULL THEN
        RETURN edate
    END IF

    RETURN first_of_next_month(edate) - 1

END FUNCTION {last_of_this_month}

fnmonth.4gl

{
    @(#)$Id: fnmonth.4gl,v 1.4 1990/04/05 11:02:03 john Exp $
    @(#)Sphinx Informix Tools: General Library
    @(#)Find 1st of next month
    @(#)Author: JL
}

FUNCTION first_of_next_month(edate)

    DEFINE
        edate   DATE,       { Effective date (frequently TODAY) }
        mm      INTEGER,    { Month number }
        yy      INTEGER     { Year }

    IF edate IS NULL THEN
        RETURN edate
    END IF

    LET mm = MONTH(edate) + 1
    LET yy = YEAR(edate)
    IF mm > 12 THEN
        LET mm = 1
        LET yy = yy + 1
    END IF

    RETURN MDY(mm, 1, yy)

END FUNCTION {first_of_next_month}

lastthismonth.spl

А вот некоторые SPL, основанные на I4GL выше:

-- @(#)$Id: lastthismonth.spl,v 1.2 2008/07/20 02:54:37 jleffler Exp $
--
-- @(#)Create last_of_this_month Stored Procedure
--
-- @(#)Version: ltmonth.4gl,v 1.4 1990/04/05 11:02:05 john
-- @(#)Sphinx Informix Tools: General Library
-- @(#)Find last day of this month
-- @(#)Author: JL
--
-- Alternative expression: 
-- (MDY(MONTH(dateval), 1, YEAR(dateval)) + 1 UNITS MONTH) - 1 UNITS DAY

CREATE PROCEDURE last_of_this_month(edate DATE DEFAULT TODAY)
    RETURNING DATE AS last_of_this_month;

    IF edate IS NULL THEN
        RETURN edate;
    END IF

    RETURN first_of_next_month(edate) - 1;

END PROCEDURE {last_of_this_month};

firstnextmonth.spl

-- @(#)$Id: firstnextmonth.spl,v 1.1 2008/07/20 02:21:13 jleffler Exp $
--
-- @(#)Create first_of_next_month Stored Procedure
--
-- @(#)Version: fnmonth.4gl,v 1.4 1990/04/05 11:02:03 john
-- @(#)Sphinx Informix Tools: General Library
-- @(#)Find 1st of next month
-- @(#)Author: JL

CREATE PROCEDURE first_of_next_month(edate DATE DEFAULT TODAY)
    RETURNING INTEGER AS first_of_next_month;

    DEFINE mm   INTEGER;    { Month number }
    DEFINE yy   INTEGER;    { Year }

    IF edate IS NULL THEN
        RETURN edate;
    END IF

    LET mm = MONTH(edate) + 1;
    LET yy = YEAR(edate);
    IF mm > 12 THEN
        LET mm = 1;
        LET yy = yy + 1;
    END IF

    RETURN MDY(mm, 1, yy);

END PROCEDURE {first_of_next_month};

Обратите внимание на альтернативное выражение. Это работает, но это невероятная смесь вычислений DATE и DATETIME.

Я имел

FUNCTION last_of_month(m,y)
DEFINE m,y SMALLINT
    RETURN MDY(m,days_in_month(m,y),y)
END FUNCTION

с соответствующей функцией days_in_month() для возврата 28,29,30,31 в зависимости от месяца и года

Для ОП вы должны создавать функцию и повторно использовать ее, а не потенциально повторять логику, как будто вы это делаете.

Что я действительно хотел прокомментировать, так это то, что в Genero мы обсуждали, стоит ли добавлять встроенные функции для выполнения этих типов вычислений. Если у каждого есть свои собственные first_of_month, last_of_month, add_months, тогда думали, что мы должны включить их в язык, а не заставлять всех заново изобретать колесо. В ходе обсуждений я узнал, что МЕСЯЦ + 1 ЕДИНИЦЫ, используемый RET в своем ответе, - это, как он говорит, високосный год. По какой-то причине я думал, что это не так и имел тенденцию избегать этого, и писать в стиле ответа JLefflers. Глядя на другой код библиотеки других клиентов 4GL/Genero, я подозреваю, что другие разработчики думали так же.

Другие вопросы по тегам