SAS - conditional macro variable - if-statement

I have a variable date=201611 and I need to create the first day of the next month in the following format '2016-12-01'. The following code works fine for the months up till 11:
%let date = 201611;
%let rok = %sysfunc(substr(&date,1,4));
%let month = %sysfunc(substr(&date,5,2));
%let xdat2_ii = &rok-%eval(&month + 1)-01;
%let xdat1 = %str(%')&xdat2_ii.%str(%');
%put &xdat1;
'2016-12-01'
I need to add some improvement to make the code working for the month 12, i.e. when the date is 201612 then to obtain '2017-01-01'.
My idea was to do it using macro, but it does not work.
%macro promenne;
%if &month < 12 %then %let xdat2_ii = &rok-%eval(&month + 1)-01
%else %if &month= 12 %then %let xdat2_ii = %eval(&rok + 1)-01-01;
%mend promenne;
Thank you for any suggestions which way to go.

When working with dates, is often easiest to use the built in date shifting functions - in this case, intnx.
/* define variable (this is a macro STRING) */
%let date=201612;
/* convert to SAS date value (numeric, num of days since 01JAN1960) */
%let dateval=%sysfunc(mdy(%substr(&date,5,2),1,%substr(&date,1,4)));
/* finally - shift to beginning of following month and format output */
%let xdat2_ii=%sysfunc(intnx(MONTH,&dateval,1,B),yymmddd10.);
%put &xdat2_ii; /* 2017-01-01 */

Related

How to call a macro inside a macro depending on conditions

I have a macro called "Comparison" that compares values from current period with the previous period, and it's working fine.
Edit to explain better: The macro Comparison will compare values from a specific account (revenues, for example) for the month T and T-1. All inside that macro works fine.
Say the current period is T. If the current month is March, June, September or December (Q1, Q2, Q3 or Q4), then I want to compare values from period T with T-1, T-1 with T-2 and T-2 with T-3. If the current month is not in the first condition, then I will only compare T with T-1. There's a variable called YEARMONTH (that can be 202210, for example) that I declare in another part of the code.
So basically I'm trying to run the Comparison macro 1 time if it's not the end of a quarter, or 3 times if it's a quarter.
I'm trying to do it as follows:
%MACRO TEST(YEARMONTH); /*20XXYY*/
%LET MONTH = %SUBSTR(&YEARMONTH,5,2);
%LET CP = &YEARMONTH.;
%LET CP_1 = &YEARMONTH. - 1;
%LET CP_2 = &YEARMONTH. - 2;
%IF &MONTH. = 3 %THEN %DO; %LET CP_3 = &YEARMONTH. - 91; %END
%ELSE %DO; %LET CP_3 = &YEARMONTH. - 3; %END;
%IF &MONTH. IN (3, 6, 9, 12) %THEN %DO;
%Comparison(CP,CP_1);
%Comparison(CP_1,CP_2);
%Comparison(CP_2,CP_3);
%END;
%ELSE %DO;
%Comparison(CP,CP_1);
%END;
%MEND TEST;
Basically I can't test it in SAS as my profile was mistakenly blocked by IT (they were meant to revoke my access to some libraries, but they revoked everything linked to SAS). Considering that the macro "Comparison" is working, will that new Macro work or are there flaws in my code?
It works a lot easier if you convert your YYYYMM string into an actual date. You have to use & before the macro variable name to pass in the values. You never defined CP_3 macro variable. You can just use the MOD() function to test if it is the last month of a quarter.
%macro test(yearmonth);
%local date month cp cp_1 cp_2 cp_3 ;
%let date=%sysfunc(inputn(&yearmonth,yymmn6.));
%let month=%sysfunc(month(&date));
%let cp = %sysfunc(putn(&date,yymmn6.));
%let cp_1 = %sysfunc(intnx(month,&date,-1),yymmn6.);
%let cp_2 = %sysfunc(intnx(month,&date,-2),yymmn6.);
%let cp_3 = %sysfunc(intnx(month,&date,-3),yymmn6.);
%comparison(&cp,&cp_1);
%if %sysfunc(mod(&month,3)) = 0 %then %do;
%comparison(&cp_1,&cp_2);
%comparison(&cp_2,&cp_3);
%end;
%mend test;
Let's make a dummy %COMPARISON() macro and test it;
317 %macro comparison(one,two);
318 %put &=one &=two;
319 %mend;
320
321 %test(202201)
ONE=202201 TWO=202112
322 %test(202203)
ONE=202203 TWO=202202
ONE=202202 TWO=202201
ONE=202201 TWO=202112

Iterate date in loop in SAS

need help on one query , I have to iterate date in do loop that is in format of yymmd6.(202112) so that once the month reach to 12 then its automatically change to next year first month.
///// code////////
%let startmo=202010 ;
%let endmo= 202102;
%macro test;
%do month= &startmo %to &endmo;
Data ABC_&month;
Set test&month;
X=&month ;
%end;
Run;
%mend;
%test;
//////////
Output should be 5 dataset as
ABC_202010
ABC_202011
ABC_202012
ABC_202101
ABC_20210
I need macro variable month to be resolved 202101 once it reached to 202012
Those are not actual DATE values. Just strings that you have imposed your own interpretation on so that they LOOK like dates to you.
Use date values instead and then it is easy to generate strings in the style you need by using a FORMAt.
%macro test(startmo,endmo);
%local offset month month_string;
%do offset = 0 to %sysfunc(intck(month,&startmo,&endmo));
%let month=%sysfunc(intnx(month,&startmo,&offset));
%let month_string=%sysfunc(putn(&month,yymmn6.));
data ABC_&month_string;
set test&month_string;
X=&month ;
format X monyy7.;
run;
%end;
%mend;
%test(startmo='01OCT2020'd , endmo='01FEB2021'd)
And if you need to convert one of those strings into a date value use an INFORMAT.
%let date=%sysfunc(inputn(202010,yymmn6.));
I would prefer to use a do while loop.
check whether the last 2 characters are 12, if so, change the month part to 01.
code
%let startmo=202010 ;
%let endmo= 202102;
%macro test;
%do %while(&startmo <= &endmo);
Data ABC_&startmo;
Set test&startmo;
X=&startmo ;
Run;
%end;
%let mon = %substr(&startmo, 5, 2);
%let yr = %substr(&startmo, 1, 4);
%if &mon = 12 %then %do;
%let m = 01;
%let startmo = %sysfunc(cat(%eval(&yr + 1), &m));
%end;
%else %do;
%let startmo = %eval(&startmo + 1);
%end;
%mend;
%test;

passing date variable to macro for sysfunc processing

below is my little problem to create a macro and passing in a date variable. Without using the date variable, it works with results as below.
%macro x();
%let i=-1;
%let dts = %sysfunc(today());
%put &dts; /*ok*/
%let yymm1 = %sysfunc(intnx(MONTH,&dts,&i));
%put &yymm1; /*ok*/
%let mth_beg = %sysfunc(intnx(MONTH,&dts,&i,B),date9.);
%let mth_end = %sysfunc(intnx(MONTH,&dts,&i,E),date9.);
%put &mth_beg &mth_end; /*01JAN2018 31JAN2018*/
/*** proc sql code below ** */
%mend;
%x();
log:
21231
21185
01JAN2018
31JAN2018
Now I create a macro around it and got the following error:
%macro x(dts1);
%let i=-1;
/*%let dts = %sysfunc(today());*/
%let dts = %sysfunc(&dts1);
%put &dts; /*ok*/
%let yymm1 = %sysfunc(intnx(MONTH,&dts,&i));
%put &yymm1; /*ok*/
%let mth_beg = %sysfunc(intnx(MONTH,&dts,&i,B),date9.);
%let mth_end = %sysfunc(intnx(MONTH,&dts,&i,E),date9.);
%put &mth_beg &mth_end; /*01JAN2018 31JAN2018*/
/*** proc sql code below ** */
%mend;
%x(16JAN2018);
ERROR: Function name missing in %SYSFUNC or %QSYSFUNC macro function reference.
JAN2018)
ERROR: Expected close parenthesis after macro function invocation not found.
))
ERROR: Expected close parenthesis after macro function invocation not found.
ERROR: Expected close parenthesis after macro function invocation not found.
,B),date9.) ,E),date9.)
I am not sure how to let SAS treat the date passed in as a recognized date. I know i probably used the sysfunc(&dts) wrongly or the date passed in need to adhere to certain format. i just want the date to replace today(). Can you help? I am a SAS newbie.
thanks
Wrap the date in " and end with a d. That will tell SAS to convert the string to a date:
%macro x(dts1);
%let i=-1;
/*%let dts = %sysfunc(today());*/
%let dts = "&dts1"d; /*Change here!*/
%put &dts; /*ok*/
%let yymm1 = %sysfunc(intnx(MONTH,&dts,&i));
%put &yymm1; /*ok*/
%let mth_beg = %sysfunc(intnx(MONTH,&dts,&i,B),date9.);
%let mth_end = %sysfunc(intnx(MONTH,&dts,&i,E),date9.);
%put &mth_beg &mth_end; /*01JAN2018 31JAN2018*/
/*** proc sql code below ** */
%mend;
%x(16JAN2018);
change %let dts = %sysfunc(&dts1); to
%let dts = %sysfunc(inputn(&dts1,date9. ));
SAS stores dates as the number of days since 01JAN1960. So if you do not attach a date format to the date value it will just look like an integer.
%let today=%sysfunc(today());
You can then use that integer anywhere you would use a date value.
%let next_month=%sysfunc(intnx(month,&today,1,b));
You can also represent dates by using a date literal. To make a date literal you represent the date value using something the DATE informat can read (like 16FEB2018, 16feb18, 16-FEB-2018, etc.) enclosed in quotes with the letter d appended.
%let today="%sysfunc(today(),date9)"d ;
%let date_string=13FEB2018;
%let date_value="&date_string"d ;
So date literals will work in SAS code and when you use the %sysfunc() macro function to call a SAS function (like INTNX) and they will work in the %sysevalf() macro function. But the %eval() macro function will not recognize date literals. So you will need to use %sysevalf() if you want use arithmetic or comparisons of date literals in macro logic.
%if %sysevalf(&today > '01JAN2018'd) %then ....
%let tomorrow=%sysevalf(&today +1);

A character operand was found in the %EVAL function or %IF condition

/* Here is the code that is having an issue*/
%macro numstats(var = ,file=, format=);
%let dsid=open(&file.,i);
%if %LENGTH(&var.) > 8 AND %VARTYPE(&dsid.,%VARNUM(&dsid.,&var.))='N' %then %do;
Proc SQL;
SQL code
quit;
end;
%mend numstats;
I am having the following error when running this code: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required.
I have played with this code extensively to try different online solutions and cannot figure out why I am still receiving this issue. Please help!
Side Note: The reason I had to add this IF statement is to try to find out whether the variable was a date variable. All my date variables are Date9. so if it is Numeric and has a length greater than 8, then I want to add date formatting that I would not add to regular numeric variables. If anyone can think of an easier way to do that, then i am open to that as well, but please help me figure out this error!
If you want to call data step functions within macro code then you need to call them with the %SYSFUNC() macro function. Here is an example of a macro function that will do that for you.
%macro varinfo
/*----------------------------------------------------------------------
Retrieve attribute of a specified variable.
----------------------------------------------------------------------*/
(ds /* Data set name */
,var /* Variable name */
,info /* information attribute to return - Default is NUM */
);
/*----------------------------------------------------------------------
Example values for INFO parameter:
NUM = variable number
LEN = length of variable
FMT = format of variable
INFMT = informat of variable
LABEL = label of variable
TYPE = type of variable (N for numeric, C for character)
------------------------------------------------------------------------
Usage Examples:
%if %varinfo(&data,NAME)
%then %put input data set contains variable NAME;
%put Variable &column in &data has type %varinfo(&data,&column,type);
------------------------------------------------------------------------
Notes:
The macro call resolves to 0 when either the data set does not exist
or the variable is not in the specified data set.
Invalid values for the INFO parameter generate a SAS ERROR message.
----------------------------------------------------------------------*/
%local dsid rc varnum;
%let dsid = %sysfunc(open(&ds));
%if (&dsid) %then %do;
%let varnum = %sysfunc(varnum(&dsid,&var));
%if (&varnum) & %length(&info) %then
%sysfunc(var&info(&dsid,&varnum))
;
%else
&varnum
;
%let rc = %sysfunc(close(&dsid));
%end;
%else 0;
%mend varinfo;
Using this your macro might become something like this:
%macro numstats(var = ,file=, format=);
%if %varinfo(&file,&var,type)=N and
DATE = %sysfunc(substrn(%varinfo(&file,&var,fmt),1,4))
%then %do;
* do something ;
%end;
%mend numstats;

SAS macros to get the output for a desired range

I'm new to SAS. Please help with my query.
I have my codes working fine for a single date where I have defined
%let yyyymmdd= 20020509;
data x;
set PQ.CP_&yyyymmdd
and then the conditions to get the desired output.
Now, I want my same codes to work for the period between startrange=-50 days before yyyymmdd and endrange=-10 days before yyyymmdd. How can I define this in the beginning to get the output for this period also as I want to use it further. Please suggest me a way to do this.
Ok - am answering this in the context of your previous question - to address just the current question, ignore the 'inner loop' below:
%macro loop(yyyymmdd=, startrange=, endrange=);
%local date x ds1 ds2 y;
%let date=%sysfunc(mdy(%substr(&yyyymmdd,5,2)
,%substr(&yyyymmdd,7,2)
,%substr(&yyyymmdd,1,4)));
/* this outer loop answers the curent question */
%do x=&startrange %to &endrange;
%let ds1=PQ.CP_%sysfunc(intnx(day,&date,&x),yymmddn8.);
%if %sysfunc(exist( &ds1 )) %then %do;
data x_%sysfunc(intnx(day,&date,&x),yymmddn8.);
set &ds1
/* this inner loop answers your previous question - remove if necessary */
%do y=-55 %to -10;
/* next date range is 55 to 10 days BEFORE the current iterative*/
/* value of (startrange-endrange) */
%let ds2=QA.TP_%sysfunc(intnx(day,&date,%eval(&y+&x)),yymmddn8.);
%if %sysfunc(exist( &ds2 )) %then %do;
&ds2
%end;
%end;
/* inner loop end */
;run;
%end;
%end;
/* outer loop end */
%mend;
%loop(yyyymmdd=20020509, startrange=-50, endrange=-10);