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);
Related
I'm unable to remove the parentheses and text within parentheses using %sysfunc(prxchange(...)). See example
%macro test(col=);
%local result;
%let result = %sysfunc(prxchange(s|\([^\)]+\)||i, -1, &col.));
%put &result.;
%mend test;
%let string = try (to) remove (this);
%test(col=%str(&string.))
ERROR: Expected close parenthesis after macro function invocation not found.
The expected output should be try remove (ignoring double spaces)
EDIT - thanks to #user667489, the simplest fix for this is
%macro test(col=);
%local result;
%let result = %sysfunc(compbl(%sysfunc(prxchange(s|%quote(\%([^\%)]+\%)||i), -1, &col.))));
%put &result.;
%mend test;
%let string = try (to) remove (this);
%test(col=%str(&string.));
I found a way of getting this to work more or less as-is:
%macro test(col=);
%local result regex;
%let regex = %sysfunc(prxparse(%str(s/\%([^\%)]+\%)//)));
%let result = %sysfunc(prxchange(®ex, -1, &col.));
%syscall prxfree(regex); /*Prevent memory leak*/
%put &result.;
%mend test;
%let string = try (to) remove (this);
%test(col=%str(&string.));
Masking the brackets within the regex with % symbols to prevent them from being parsed as SAS code and using a separate prxparse seems to do the trick.
Not sure if you can fix this because of the nature of how %sysfunc() has to convert the macro code into values to push in the function you are trying to call.
Why not just leave the PRXCHANGE() function call in actual SAS code instead?
For example you could have your macro generate a DATA step. I would recommend just passing in the NAME of the macro variable that has the value text and the NAME of the macro variable you want to assign the result into.
%macro test(invar,outvar);
%if not %symexist(&outvar) %then %global &outvar;
data _null_;
call symputx("&outvar",prxchange('s|\([^\)]+\)||i', -1,symget("&invar")));
run;
%mend test;
%let string = try (to) remove (this);
%test(invar=string,outvar=result);
%Put &=result;
Also try this:
%macro string(string);
%local new_string;
%let new_string=%sysfunc(prxchange(s/\s?\(\w+\)//,-1,&string));
%put &new_string;
%mend;
%string(%str(try (to) remove (this)));
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 */
When calling CATT() function with %sysfunc, is there a way to stop it from evaluating an expression?
For example given the code:
%let date=10-13-2015;
%put %sysfunc(catt(The date Is:,&date));
I would like it to return:
The date Is:10-13-2015
Because 10-13-2015 is just a text string. But instead CATT() sees hyphen as a subtraction sign and evaluates it as a numeric expression, returning:
The date Is:-2018
I have tried macro quoting, but doesn't change anything, I suppose because I need to somehow hide the values from CATT(). Seems if any argument to CATT looks like an expression, it will be treated as such.
Another example:
%let value=2 and 3;
%put %sysfunc(catt(The value Is:,&value));
The value Is:1
Provided you can do so, just remove the comma - there's no need to separate it into an individual parameter (unless you're using catx() rather than catt():
%let date=10-13-2015;
%put %sysfunc(catt(The date Is: &date));
Personally, I think the best way to work is to store the date as a SAS date value and then use the second (optional) parameter of %sysfunc to apply the formatting. This provides better flexibility.
%let date = %sysfunc(mdy(10,13,2015));
%put The date Is: %sysfunc(sum(&date),mmddyyd10.);
If you are insistent on the original approach and are using catx(), then I don't know how to do it exactly. The closest I could get was to insert a piece of text so it couldn't be interpreted as an expression, and then remove that text afterwards using tranwrd. Pretty, ugly, and it leaves a space:
%let date=10-13-2015;
%let tmp=%sysfunc(catx(#, The date Is: , UNIQUE_STRING_TO_REMOVE&date ));
%let want=%sysfunc(tranwrd(&tmp, UNIQUE_STRING_TO_REMOVE, ));
%put &want;
Gives:
The date Is:# 10-13-2015
I also tried every combination of macro quoting, and scanned through the entire SAS function list and couldn't see any other viable options.
I don't see an easy way around this, unfortunately. I do see that you could in theory pass this through an FCMP function, though since FCMP doesn't allow true variable arguments, that isn't ideal either, but...
proc fcmp outlib=work.funcs.funcs;
function catme(delim $, in_string $) $;
length _result $1024;
length _new_delim $1;
_new_delim = scan(in_string,1,delim);
do _i = 1 to countc(in_string,delim);
_result = catx(_new_delim, _result, scan(in_string,_i+1,delim));
end;
return(_result);
endfunc;
quit;
options cmplib=work.funcs;
%let date=10-13-2015;
%put %sysfunc(catme(|,:|The date Is| &date.));
Or add quotes to the argument and then remove them after the CATx.
%sysfunc(dequote(%sysfunc(catt(.... ,"&date."))))
All messy.
The problem with %SYSFUNC() evaluating the arguments is not limited to the CAT() series of functions. Any function that accepts numeric values will result in SAS attempting to evaluate the expression provided.
This can be a useful feature. For example:
%let start_dt=10OCT2012 ;
%put %sysfunc(putn("&start_dt"d +1,date9));
You don't need to use CAT() functions to work with macro variables. Just expand the values next to each other and the are "concatenated".
%let date=10-13-2015;
%put The date Is:&date;
If you want to make a macro that works like the CATX() function then that is also not hard to do.
%macro catx /parmbuff ;
%local dlm return i ;
%if %length(&syspbuff) > 2 %then %do;
%let syspbuff = %qsubstr(&syspbuff,2,%length(&syspbuff)-2);
%let dlm=%qscan(&syspbuff,1,%str(,),q);
%let return=%qscan(&syspbuff,2,%str(,),q);
%do i=3 %to %sysfunc(countw(&syspbuff,%str(,),q));
%let return=&return.&dlm.%qscan(&syspbuff,&i,%str(,),q);
%end;
%end;
&return.
%mend catx;
%put %catx(|,a,b,c);
a|b|c
%put "%catx(",",a,b,c,d)";
"a","b","c","d"
Slightly less insane function-style macro without the dosubl:
%macro catx() /parmbuff;
%local rc dlm i params OUTSTR QWORD outstr;
%let SYSPBUFF = %qsubstr(&SYSPBUFF,2,%length(&SYSPBUFF)-2);
%let dlm = %qscan(&SYSPBUFF,1,%str(,));
%let params = %qsubstr(&SYSPBUFF,%index(&SYSPBUFF,%str(,))+1);
%let i = 1;
%let QWORD = %scan(&PARAMS,&i,%str(,));
%let OUTSTR = &QWORD;
%do %while(&QWORD ne);
%let i = %eval(&i + 1);
%let QWORD = %scan(&PARAMS,&i,%str(,));
%if &QWORD ne %then %let OUTSTR = &OUTSTR.&DLM.&QWORD;
%end;
%unquote(&OUTSTR)
%mend catx;
%put %catx(%str( ),abc,10 - 1 + 2,def);
Somewhat more insane but apparently working option - use %sysfunc(dosubl(...)) and lots of macro logic to create a function-style macro that takes input in the same way as %sysfunc(catx(...)), but forces catx to treat all input as text by quoting it and calling it in a data step.
%macro catxt() /parmbuff;
%local rc dlm i params QPARAMS QWORD outstr;
%let SYSPBUFF = %qsubstr(&SYSPBUFF,2,%length(&SYSPBUFF)-2);
%let dlm = %qscan(&SYSPBUFF,1,%str(,));
%let params = %qsubstr(&SYSPBUFF,%index(&SYSPBUFF,%str(,))+1);
%let i = 1;
%let QWORD = "%scan(&PARAMS,&i,%str(,))";
%let QPARAMS = &QWORD;
%do %while(&QWORD ne "");
%let i = %eval(&i + 1);
%let QWORD = "%scan(&PARAMS,&i,%str(,))";
%if &QWORD ne "" %then %let QPARAMS = &QPARAMS,&QWORD;
%end;
%let rc = %sysfunc(dosubl(%str(
data _null_;
call symput("OUTSTR",catx("&dlm",%unquote(&QPARAMS)));
run;
)));
&OUTSTR
%mend catxt;
%put %catxt(%str( ),abc,10 - 1 + 2,def);
Although this uses a data step to execute catx, dosubl allows the whole thing to be run in any place where you could normally use %sysfunc(catx(...)).
/* 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;
How do I print out the data type of a macro variable in the log
%macro mymacro(dt2);
%LET c_mth = %SYSFUNC(intnx(month,&dt2.d,-1,e),date9.) ;
%put &c_mth;
%mend;
mymacro('01sep2014')
I have a bunch of macro variables assigned using a %let or into:
my problem is I'm trying to do a bunch of boolean condition on dates but I suspect that some of my variables are strings and some are dates
I have casted them in my code but to triple check there is surely a way to return something to the log
I want something similar to using str() or mode() or is.numeric() in R
H,
The SAS macro language is weird. : )
As Reeza said, macro variables do not have a type, they are all text.
But, if you use Boolean logic (%IF statement), and both operands are integers, the macro language will do a numeric comparison rather than a character comparison.
So you can use the INPUTN() function to convert the date strings to SAS dates (number of days since 01Jan1960), and then compare those. Here's an example, jumping off from your code:
%macro mymacro(dt1,dt2);
%local c_mth1 c_mth2 n_mth1 n_mth2;
%let c_mth1 = %sysfunc(intnx(month,&dt1.d,-1,e),date9.) ;
%let c_mth2 = %sysfunc(intnx(month,&dt2.d,-1,e),date9.) ;
%let n_mth1 = %sysfunc(inputn(&c_mth1,date9.)) ;
%let n_mth2 = %sysfunc(inputn(&c_mth2,date9.)) ;
%put &c_mth1 -- &n_mth1;
%put &c_mth2 -- &n_mth2;
%if &n_mth1<&n_mth2 %then %put &c_mth1 is before &c_mth2;
%else %put &c_mth1 is NOT before &c_mth2;
%mend;
Log from a sample call:
236 %mymacro('01feb1960','01mar1960')
31JAN1960 -- 30
29FEB1960 -- 59
31JAN1960 is before 29FEB1960
--Q.
Macro variables do not have a type, they are all text.
You have to make sure the variable is passed in a way that makes sense to the program and generates valid SAS code.
%let date1=01Jan2014;
%let date2=31Jan2014;
data _null_;
x = "&date1"d > "&date2"d;
y = "&date2"d > "&date1"d;
z = "&date2"d-"&date1"d;
put 'x=' x;
put 'y=' y;
put 'z=' z;
run;
Log should show:
x=0
y=1
z=30
If your macro variables resolve to date literals, you can use intck combined with %eval to compare them, e.g.
%let mvar1 = '01jan2015'd;
%let mvar2 = '01feb2015'd;
/*Prints 1 if mvar2 > mvar1*/
%put %eval(%sysfunc(intck(day,&mvar1,&mvar2)) > 0);