Agree with others, that code is usually simpler and more readable if you use SAS date values.
In this case, if you pass in a SAS date for start and end, then you can increment by month within the loop using INTNX.
Note I had to add 1 to &end, because the macro compiler won't let you use %BY 0 for fear of an infinite loop. This could also be done as a %DO %WHILE or %DO %UNTIL, if that hack is unappealing to you.
%macro foo(start,end) ;
%local date ;
%do date=%sysevalf(&start) %to %sysevalf(&end+1); %*Add a day to end because do-loop will increment by 1;
%put %sysfunc(putn(&date,yymmn6)) ;
%let date=%sysfunc(intnx(month,&date,1)) ;
%end ;
%mend foo ;
%foo("01Jun2023"d,"01Mar2024"d)
Unlike much of the advice you have been given on this topic, from experienced and thoughtful respondents, in this instance I do not subscribe to the "always use date values" in the naming of your monthly datasets.
I have already recommended generating a list of datasets, concatenated into a dataset view, and appended once, which I think is likely more efficient.
But if you MUST generate macro loop replicating a sequence of months in a YYMM expression, it is straightforward. I have frequently used the approach below, which has a major loop of YY over years, then a minor loop of MM over months. The month loop goes from 1 to 12, unless it is part of the first or last year:
%macro rrr (yymm_beg=,yymm_end=);
%do yy=%substr(&yymm_beg,1,2) %to %substr(&yymm_end,1,2);
%let mm_beg= %sysfunc(ifn(&yy=%substr(&yymm_beg,1,2),%substr(&yymm_beg,3,2),1));
%let mm_end= %sysfunc(ifn(&yy=%substr(&yymm_end,1,2),%substr(&yymm_end,3,2),12));
%do mm=&mm_beg %to &mm_end;
%let mm=%sysfunc(putn(&mm,z2.)); /*Insert leading zero*/
proc append data=Mon_tbl&yy&mm base=want force;
run;
%end;
%end;
%mend;
%rrr(yymm_beg=2306,yymm_end=2403);
I don't see how your code accounts for the fact the the month after 2312 is 2401
But anyway, the code you use of pulling apart strings uses logic that is not as straightforward as having SAS figure out what is the next month after a given month. This is much simpler to understand and IMHO much simpler to program.
@PaigeMiller wrote:
I don't see how your code accounts for the fact the the month after 2312 is 2401
That is accounted for in constructing macrovar MM_BEG, which is done once for each year. Similarly once per year, the macro MM_END is constructed. If you run the macro below, you'll see the proper sequence of 2312 followed by 2401:
%macro rrr (yymm_beg=,yymm_end=);
%do yy=%substr(&yymm_beg,1,2) %to %substr(&yymm_end,1,2);
%put &=yy ;
%let mm_beg= %sysfunc(ifn(&yy=%substr(&yymm_beg,1,2),%substr(&yymm_beg,3,2),01));
%let mm_end= %sysfunc(ifn(&yy=%substr(&yymm_end,1,2),%substr(&yymm_end,3,2),12));
%do mm=&mm_beg %to &mm_end;
%let mm=%sysfunc(putn(&mm,z2.)); /*Insert leading zero*/
%put %str( ) YYMM becomes &yy&mm;
%end;
%end;
%mend;
%rrr(yymm_beg=2306,yymm_end=2403);
@PaigeMiller also wrote:
But anyway, the code you use of pulling apart strings uses logic that is not as straightforward as having SAS figure out what is the next month after a given month. This is much simpler to understand and IMHO much simpler to program.
After thinking about your comment, I realized I had forgotten about %DO %WHILE, which I find to be an easier way to use sas date capacities than iterative %DO, and it avoids the need to generate leading zeroes for MM=1 through 9.
%macro rrr(yymm_beg=,yymm_end=);
%let yymm=&yymm_beg;
%let date= %sysfunc(inputn(&yymm.01,yymmdd6.));
%do %while (&yymm <= &yymm_end);
%put &=yymm; /*User code here */
%let date = %sysfunc(intnx(month,&date,1));
%let YYMM=%sysfunc(putn(&date,YYMMN4.));
%end;
%mend rrr;
%rrr(yymm_beg=2306,yymm_end=2403);
@mkeintz wrote:
%macro rrr(yymm_beg=,yymm_end=); %let yymm=&yymm_beg; %let date= %sysfunc(inputn(&yymm.01,yymmdd6.)); %do %while (&yymm <= &yymm_end); %put &=yymm; /*User code here */ %let date = %sysfunc(intnx(month,&date,1)); %let YYMM=%sysfunc(putn(&date,YYMMN4.)); %end; %mend rrr; %rrr(yymm_beg=2306,yymm_end=2403);
I like this solution very much.
Make your macro variables local, or you'll have a rude awakening if someone uses yymm or date as macro variables in the calling context.
Never much like to use looping that requires the user to increment the counter on their own.
Especially for a situation where the number of loops that are needed is know (or easily calculated as in this case).
That works well for MONTH intervals if for some reason you did not have access to functions that know how to handle dates.
But I wouldn't hide simple IF/THEN logic in the middle of a function.
%let mm_beg=1;
%let mm_end=12;
%if &yy=%substr(&yymm_beg,1,2) %then %let mm_beg=%substr(&yymm_beg,3,2);
%if &yy=%substr(&yymm_end,1,2) %then %let mm_end=%substr(&yymm_end,3,2);
But since your code is already using %SYSFUNC() to call SAS functions why not just use INPUTN(), INTCK() and INTNX()?
%macro rrr(yymm_beg=,yymm_end=);
%local beg_date end_date offset yymm ;
%let beg_date=%sysfunc(inputn(&yymm_beg,yymmn4.));
%let end_date=%sysfunc(inputn(&yymm_end,yymmn4.));
%do offset=0 %to %sysfunc(intck(month,&beg_date,&end_date));
%let yymm=%sysfunc(intnx(month,&beg_date,&offset),yymmn4.);
%put yymm=&yymm;
/*
proc append data=Mon_tbl&yymm base=want force;
run;
*/
%end;
%mend;
Which has the added advantage that the same basic code structure can be used to iterate by almost any other DATE, TIME or DATETIME interval.
Good news: We've extended SAS Hackathon registration until Sept. 12, so you still have time to be part of our biggest event yet – our five-year anniversary!
Learn how use the CAT functions in SAS to join values from multiple variables into a single value.
Find more tutorials on the SAS Users YouTube channel.
Ready to level-up your skills? Choose your own adventure.