Problema de FECHA SAS: cálculo de & # 8220; MES MÁS CERCANO & # 8221;
Pregunta
Necesito calcular una cifra que equivale al número 'más cercano' de meses entre dos fechas. Sin embargo, la función SAS estándar (INTCK) no está diseñada para considerar el DÍA de sus parámetros de fecha (por ejemplo, el código a continuación se resuelve en 0 cuando necesito redondear a 1).
¿Cuál es la forma "más perfecta" de resolver este problema?
data _null_;
x="01APR08"d;
y="28APR08"d;
z=intck('MONTH',x,y);
put z= ;
run;
EDITAR: respuesta al comentario de Martins.
Redondearía a 0 meses. No creo que la frontera sea relevante. La función que intento replicar (NEAREST_MONTHS) proviene de DCS (aplicación de profeta Sungard). Ahora estoy esperando la oportunidad de realizar algunas pruebas dentro de la aplicación para comprender mejor cómo trata las fechas (publicará los resultados aquí)).
El archivo de ayuda contiene lo siguiente: Categoría Fecha
Descripción
Devuelve la diferencia entre dos fechas al número de meses más cercano. Si la segunda fecha es posterior a la primera, se devuelve 0.
Sintaxis
NEAREST_MONTHS (Later_Date, Earlier_Date)
Tipo de retorno Entero
Ejemplos
NEAREST_MONTHS (fecha1, fecha2) Devuelve 8 si date1 es 20/3/1997 y date2 es 23/7/1996
NEAREST_MONTHS (fecha1, fecha2) Devuelve 26 si date1 es 20/3/1997 y date2 es 1/2/1995
Solución
Escribí esto como una función que creo que calcula de la misma manera que la aplicación DCS. Utiliza algunas características que son nuevas para SAS en la versión 9.2, incluidas las alineaciones continuas en las fechas. También funciona hacia adelante o hacia atrás en el tiempo (es decir, da un número entero negativo si before_date es posterior a later_date). Utilicé más de 15 días más allá del intervalo como límite para redondear al próximo mes, pero puede ajustar esto si lo prefiere.
proc fcmp outlib=work.myfuncs.dates;
function nearest_months(later_date,earlier_date);
/* Return missing if inputs are missing */
if (earlier_date eq . ) OR (later_date eq . ) then
nearest_month=.;
else do;
/* Use 'cont' argument for continuous dates */
months=intck('MONTH',earlier_date,later_date,'cont');
if months < 0 then months=months+1;
days= later_date - intnx('month', earlier_date,months,'same');
/* Handle negatives (earlier dates) */
if months < 0 then do;
if days < -15 then months=months-1;
nearest_month=months;
end;
else do;
if days > 15 then months + 1;
nearest_month=months;
end;
end;
return(nearest_month);
endsub;
run;
options cmplib=work.myfuncs;
data _null_;
x=nearest_months('20Mar1997'd, '23JUL1996'd);
put x=;
x=nearest_months('20Mar1997'd, '01FEB1995'd);
put x=;
run;
Esto da lo mismo que su referencia:
x=8
x=26
Otros consejos
Puede usar INTNX
para ver si se debe redondear hacia arriba o hacia abajo, por ejemplo
data _null_;
format x y date9. z 8.;
x="01APR08"d;
y="28APR08"d;
z=intck('MONTH',x,y);
* wl is x + z months;
wl=intnx('MONTH',x,z);
* wu is x + (z+1) months;
wu=intnx('MONTH',x,z+1);
* If y is closer to wu, then adjust z by 1;
if (abs(y-wu) lt abs(y-wl)) then z = z+1;
put x y z=;
run;
Si define un mes como 30 días, redondeará 15 días o menos a 0 meses y 16 días o más a 1 mes. Esto se puede lograr de la siguiente manera:
data _null_;
format x y date9. z 8.;
x="14FEB09"d;
y="02MAR09"d;
z=round(intck('DAY',x,y)/31);
put x y z=;
run;
También puede tomar el enfoque para contar los meses completos (" primero, primero al último ") en el intervalo, y luego sumar los días restantes para ver si suman 0, 1 o 2 meses. Así:
data _null_;
format x y date9. z 8.;
x="01FEB09"d;
y="31MAR09"d;
if day(x)=1 then do;
z=intck('MONTH',x,intnx('MONTH',y,0,'BEGINNING'))
+ round((intck('DAY',intnx('MONTH',y,0,'BEGINNING'),y))/31);
end;
else do;
z=intck('MONTH',intnx('MONTH',x,1,'BEGINNING'),intnx('MONTH',y,0,'BEGINNING'))
+ round((intck('DAY',x,intnx('MONTH',x,1,'BEGINNING'))+intck('DAY',intnx('MONTH',y,0,'BEGINNING'),y))/31);
end;
put x y z=;
run;
El primer método es más fácil de entender y mantener, pero el segundo es más preciso para intervalos grandes (01FEB06 a 01FEB09 son 36 meses, pero el método 1 le dirá que solo son 35).