SAS macro mod function callout issue - sas

I'm trying to adjust the reporting period between 1 month and 3 months (full quarter) based on time of the month. Based on the date below, I should be getting the period between 1/1 and 3/31, but I'm getting a logic error when I run the following code, any advice on how to fix it?
ERROR: Expected close parathesis after macro function invocation not found.
ERROR: Required operator not found in expression: 0 )
ERROR: SKipping to next %END statement.
%let obsdate = '31Mar2021'd;
%let current_qtr_first_day = intnx('month', &obsdate, -2, 'B');
%let current_qtr_last_day = intnx('month', &obsdate, 0, 'E');
%let current_month_first_day = intnx('month', &obsdate, 0, 'B');
%let current_month_last_day = intnx('month', &obsdate, 0, 'E');
%if %sysfunc(MOD(month(&obsdate),3)=0 ) %then %do;
%let startdt = &obsdate_current_qtr_first_day;
%let enddt = &obsdate_current_qtr_last_day;
%end;
%else %do;
%let startdt = &obsdate_current_month_first_day;
%let enddt = &obsdate_current_month_last_day;
%end;

A few more issues than I initially realized so listing all below but overall, you shouldn't be doing this in this manner. Use a data step and CALL SYMPUTX() to create the macro variable at the end instead - less buggy and infinitely easier to code.
EVERY function call in a macro requires the %SYFUNC() wrapped around it - INTNX(), MOD(), MONTH()
Missing semicolons (Line 2, 4, 7, 9)
%DO is spelled incorrectly (DOL)
I don't know what you would want here: &obsdate_current_qtr_first_day You have no macro variable shown with that name but you have two with each portion of the name? Did you want to concatenate those values?
Functions used in macros should not have the parameters within quotes ('month' versus month)
Use the options mprint; when writing/developing macros so you can see the values in the log and get more information to help you debug the issues at hand.
options mprint;
%let obsdate = '31Mar2021'd;
%let current_qtr_first_day = %sysfunc(intnx(month, &obsdate, -2, B));
%let current_qtr_last_day = %sysfunc(intnx(month, &obsdate, 0, E));
%let current_month_first_day = %sysfunc(intnx(month, &obsdate, 0, B));
%let current_month_last_day = %sysfunc(intnx(month, &obsdate, 0, E));
%if %sysfunc(MOD(%sysfunc(month(&obsdate)), 3))=0 %then %do;
%let startdt = &obsdate._&current_qtr_first_day;
%let enddt = &obsdate._&current_qtr_last_day;
%end;
%else %do;
%let startdt = &obsdate._&current_month_first_day;
%let enddt = &obsdate._&current_month_last_day;
%end;
EDIT: Here's a data step solution that can be simplified once you have it set up for what you want.
%macro get_report_dates(obsdate=);
data _null_;
*determine if month or quarterly reporting is needed;
if month(&obsdate) in (3, 6, 9, 12) then interval = 'QTR';
else interval = 'MONTH';
start_date = intnx(interval, &obsdate, 0, 'b');
end_date = intnx(interval, &obsdate, 0, 'e');
format start_date end_date date9.;
*creates global macro variables available outside this macro;
*change format here to have it displayed as desired (eg date9);
call symputx('startdt', put(start_date, yymmddd10.), 'g');
call symputx('enddt', put(end_date, yymmddd10.), 'g');
run;
%mend;
*test for a quarter;
%get_report_dates(obsdate="01Mar2021"d);
%put &startdt.;
%put &enddt.;
*test for a month;
%get_report_dates(obsdate="01Feb2021"d);
%put &startdt.;
%put &enddt.;

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;

Loop between dates

I created below macro to generate few datasets based on date macro.
%macro pull(date);
proc sql;
create table new&date as
select * from acct
where date=&date.;
quit;
%mend;
So if i want to create dataset for 20170101 20170201 20170301 20170401 20170501, all i can do is use below macro
%macro pull(20170101)
%macro pull(20170201)
%macro pull(20170301)
%macro pull(20170401)
%macro pull(20170501)
What i am planning now is create two macro variables
%let begin=20170101;
%let end =20170501;
and create datasets based on begin and end using loop. Is it possible to do that.So what i am trying to do is give start and end date as macro variable and pull records between begin and end date from acct dataset and create separate datasets for each month between start and end dates
Note dataset have monthly dates for each year.
Below is the code i am trying
%let beg="01jan2000"d;
%let end="01jan2001"d;
%macro Test;
%do date=&beg. %to &end.;
proc sql;
create table IPw_&date. as
select *
from sample
where date=&date. quit;
%end;
%mend;
%Test;
When date information must be inferred from values that are not SAS date values you will need to input the information to get a date value, and put the values iterated over to get the desired non date representation.
This example demonstrates
INPUTN function to parse the YYYYMMDD arguments into date values using informat YYMMDD8.
INTNX function to compute 1st of the month of the date values
PUTN function to convert a date value to a YYYYMMDD representation using format YYMMDDN8.
%DO %WHILE statement for iterating
INTNX function to advance the iteration variable to the start of the next month
Code
%macro pull(yyyymmdd);
%local out;
%let out = pull_&yyyymmdd;
data &out;
pull_date = input ("&yyyymmdd", yymmdd8.);
format pull_date yymmdd10.;
run;
%mend;
%macro pull_each_month(begin=, end=);
%local
begin_date end_date
begin_month end_month
pull_date pull_ymd
;
%put NOTE: &=begin &=end;
%let begin_date = %sysfunc(inputn(&begin,yymmdd8.));
%let end_date = %sysfunc(inputn(&end,yymmdd8.));
%put NOTE: &=begin_date &=end_date;
%let begin_month = %sysfunc(intnx(month,&begin_date,0));
%let end_month = %sysfunc(intnx(month,&end_date,0));
%put NOTE: &=begin_month &=end_month;
%let pull_month = &begin_month;
%do %while (&pull_month <= &end_month);
%let pull_ymd = %sysfunc(putn(&pull_month,yymmddn8.));
%put NOTE: Invoking pull for &=pull_month &=pull_ymd;
%pull (&pull_ymd)
%let pull_month = %sysfunc(INTNX(MONTH,&pull_month,1));
%end;
%mend;
%pull_each_month (
begin = 20170101
, end = 20170501
)
%macro pull_each_month(begin=, end=);
%local
begin_date end_date
begin_month end_month
pull_date pull_ymd
;
%put NOTE: &=begin &=end;
%let begin_date = %sysfunc(inputn(&begin,yymmdd8.));
%let end_date = %sysfunc(inputn(&end,yymmdd8.));
%put NOTE: &=begin_date &=end_date;
%let begin_month = %sysfunc(intnx(month,&begin_date,0));
%let end_month = %sysfunc(intnx(month,&end_date,0));
%put NOTE: &=begin_month &=end_month;
%let pull_month = &begin_month;
%do %while (&pull_month <= &end_month);
%let pull_ymd = %sysfunc(putn(&pull_month,yymmddn8.));
%put NOTE: Invoking pull for &=pull_month &=pull_ymd;
%let pull_month = %sysfunc(INTNX(MONTH,&pull_month,1));
%end;
%mend;
%pull_each_month (
begin = 20170101
, end = 20170501
)
%macro pull(begin,end);
%let i=0;
%let begin=%sysfunc(inputn(&begin,anydtdte9.));
%let end=%sysfunc(inputn(&end,anydtdte9.));
%do %until (&begin=&end);
%let begin=%sysfunc(intnx(month,&begin,&i));
%let date=%sysfunc(putn(&begin,yymmddn8.));
proc sql;
create table new&date as
select * from acct where date=&date.;
quit;
%let i=%eval(&i+1);
%end;
%mend;
%pull(20170101,20170501)

SAS - conditional macro variable

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 */

regex capturing inside a macro definition (SAS 9.4)

Here is a paraphrased version of my code:
options nofmterr symbolgen;
%macro data_checks(dset,newset);
%if &newset= %then
%let newset = new;
proc contents data=&dset out=work.&newset noprint;
run;
%local re yr;
%let re = %sysfunc(prxparse('/_y(\d\d?)$/'));
%let yr = %sysfunc(prxposn(&re, 1, &dset));
%put &yr;
%mend data_checks
%data_checks(owners.ownersclean_y10, new_dataset);
As far as I can tell, the re string I have is fine. SYMBOLGEN output in the log is telling me that &re is what appears to be a random integer between 1 and 10 each time I run it and &yr appears to resolve to an empty string each time.
What am I doing wrong? &yr is supposed to resolve to 10.
You've got two problems here:
You must use PRXMATCH before PRXPOSN (or a few other match-making functions). See the documentation for PRXPOSN.
You should not use ' in the expression.
This works:
options nofmterr symbolgen;
%macro data_checks(dset,newset);
%if &newset= %then
%let newset = new;
%local re yr;
%let re = %sysfunc(prxparse(/_y(\d\d?)$/));
%let rc = %sysfunc(prxmatch(&re,&dset));
%let yr = %sysfunc(prxposn(&re, 1, &dset));
%put &=yr;
%mend data_checks;
%data_checks(owners.ownersclean_y10, new_dataset);