4397-2020
4397-2020
ABSTRACT
The first thing you need to know is that SAS® software stores dates and times as numbers.
However, this is not the only thing you need to know. This paper is designed to give you a
solid base for working with dates and times in SAS. It introduces you to functions and
features that enable you to manipulate your dates and times with surprising flexibility. This
paper shows you some of the possible pitfalls with dates (and times and datetimes) in your
SAS code and how to avoid them. We show you how SAS handles dates and times through
examples, including the ISO 8601 formats and informats, and how to use dates and times in
TITLE and FOOTNOTE statements. The paper closes with a brief discussion of Excel
conversions.
INTRODUCTION
Dates are counted in days from a zero point of January 1, 1960. Times are counted in
seconds from a zero point of midnight of the current day, and datetimes are counted in
seconds since midnight, January 1, 1960. Each day that passes increments the day counter
by one, and each second that passes increments the time and datetime counters by one.
This makes it easy to calculate durations in days and seconds via simple subtraction. Then it
is a matter of dividing by the appropriate value to get the duration in regular time intervals
(such as hours or minutes,) or using a SAS function to obtain durations in irregularly spaced
time intervals (such as months or years.)
1
DATA _NULL_;
date1 = 'January 14, 1967';
date2 = 'September 4, 2014';
days_in_between = date2 - date1;
PUT days_in_between = ;
RUN;
These are the NOTEs you would get in the SAS log.
NOTE: Character values have been converted to numeric values at the
places given by: (Line):(Column). 19 4:27
NOTE: Invalid numeric data, date2='September 4, 2014' , at line 4
column 19.
NOTE: Invalid numeric data, date1='January 14, 1967' , at line 4 column
27.
days_in_between = • (the dreaded missing value dot)
Example 1: What Happens When You Try to Use a Character Value as a Date
In order to tell SAS about a specific date, you use a SAS "date literal." The date literals for
the two dates above are "14JAN1967"d and "04SEP2014"d. The letter "d" at the end tells
SAS that this is a date, not a string of characters. When we make the change in the code,
we get the result in Example 2:
DATA _NULL_;
date1 = "14jan1967"d;
date2 = "04sep2014"d;
days_in_between = date2 - date1;
PUT days_in_between = ;
RUN;
days_in_between=17400
2
DATA date_length;
LENGTH len3 3 len4 4 len5 5;
len3 = "02AUG2006"d + 2;
len4 = len3;
len5 = len3;
FORMAT len3 len4 len5 mmddyy10.;
RUN;
HISTORICAL DATES
SAS can go all the way back to January 1, 1582, so you will likely be able to work with
historical dates. However, historical dates have the potential to produce incorrect values in
SAS. You may not get a missing value, but make sure you check your century. The
YEARCUTOFF option gives you the capability to define a 100-year range for two-digit year
values. The default value for the YEARCUTOFF option in version 9.4 of SAS is 1926, giving
you a range of 1926-2025. Let's demonstrate with Example 4, using date literals:
OPTIONS YEARCUTOFF=1926; /* SAS System default */
DATA yearcutoff1;
yearcutoff = "SAS System Default: 1926";
date1 = "08AUG06"d;
date2 = "15JUN48"d;
date3 = "04jan69"d;
RUN;
OPTIONS YEARCUTOFF=1840;
DATA yearcutoff2;
yearcutoff = "1840";
date1 = "08AUG06"d;
date2 = "15JUN48"d;
date3 = "04jan69"d;
RUN;
YEARCUTOFF date1 date1 Value date2 date2 Value date3 date3 Value
value literal in SAS literal in SAS literal in SAS
1926 08AUG06 08/08/2006 15JUN48 06/15/1948 04JAN69 01/04/1969
1840 08AUG06 08/08/1906 15JUN48 06/15/1848 04JAN69 01/04/1869
Example 4: Effects of the YEARCUTOFF= Option on Identical Date Strings
As you can see, identical date literals can give you completely different results based on the
value of this option. Any two-digit year that SAS has to translate, whether it is from a date
literal as shown in the above example, an ASCII file being processed with the INPUT
statement and an informat, or even the INPUT() function and an informat will be affected by
this option. However, dates that are already stored as SAS dates are NOT affected by this
3
option. SAS dates are simply stored as the number of days from January 1, 1960, and so
the two-digit vs. four-digit year doesn't matter. The lesson here is to check your dates
before and after processing.
Example 5: The Importance of Context When Using Formats with SAS Date and
Time Values
Notice that the datetime value gives you several asterisks when you try to format it as a
date. The date represented by the value 1,737,820,327 is so far in the future that it cannot
be represented by a four-digit year, but that’s the only blatant indication that something's
not quite right. Why is there a discrepancy on the others? When you try to translate a date
value with a time format, you are translating days since January 1, 1960 using something
designed to translate seconds since midnight. 19,943 seconds after midnight is 5:32:23 in
the morning. If you translate 19,943 as seconds after midnight of January 1, 1960, which is
the definition of a datetime, you get 5:32:23 AM on January 1, 1960. Similarly, if you
translate 42,180 as days since January 1, 1960, you get June 26, 2075. Finally, note the
cell in italics. There is absolutely nothing to indicate anything is wrong, but it is incorrect.
Why do we get a normal-looking time? The TIMEAMPM. format gives times from 12:00 AM
to 11:59 PM, so any value greater than 86,400 (the number of seconds in a day) just cycles
into the next day. Therefore, you are getting the result of the calculation
MOD(1737820327,86400), which is 57,127, and translates into a time of 3:52:07 PM using
the time scale of seconds after midnight. While it may seem correct, you are still using the
wrong units: seconds since midnight of the current day for a value that is using seconds
since midnight, January 1, 1960.
4
panic. You can create and use a custom format to show off your dates. There are two ways
to do this and they both require using the FORMAT procedure. The first way uses the VALUE
statement. You define a range for the values using date, time, or datetime constants, and
then you can tell SAS what to print out instead of the date. Sample Code 2 creates a format
to display whether a contract is scheduled for arbitration or renegotiation
1 PROC FORMAT;
2 VALUE contrct
3 LOW-'15nov2013'd="EXPIRED"
4 '16NOV2013'd-'15nov2014'd="RENEGOTIATION"
5 '16NOV2014'd - '15nov2016'd = "ARBITRATION"
6 '16nov2016'd-high=[MONYY7.]; /* INSTRUCTS SAS TO USE THE MONYY7.
FORMAT FOR VALUES BEYOND NOVEMBER
16, 2016 */
7 RUN;
Sample Code 2: Creating a Format to Display Text Instead of a Date
This is a look at a few records of the raw data:
Contract Number Expiration Date
5829014 11/06/2013
9330471 09/21/2015
6051271 04/11/2015
Instead of printing the date for the variable EXP_DATE, our format classifies the date values
and translates them into categorical text. We are going to reformat the display of the raw
date as well. Using aliases in the COLUMNS statement below, Example 6 shows how to turn
these two columns from the dataset we create into an ordered list by date using differing
formats:
PROC REPORT DATA=contracts NOWD;
COLUMNS exp_date_raw contract_num exp_date_raw=exp_date
exp_date_raw=exp_date_disp;
DEFINE exp_date_raw / NOPRINT ORDER;
DEFINE contract_num / "Contract Number";
DEFINE exp_date / FORMAT=contrct. "Negotiation Status at End-of-Term";
DEFINE exp_date_disp / FORMAT=worddate. "Expiration Date";
RUN;
5
So where can you go wrong here? Several places, actually. Let's examine Sample Code 2:
1 PROC FORMAT;
2 VALUE contrct
3 LOW-'15nov2013'd="EXPIRED"
4 '16NOV2013'd-'15nov2014'd="RENEGOTIATION"
5 '16NOV2014'd - '15nov2016'd = "ARBITRATION"
6 '16nov2016'd-high=[MONYY7.]; /* INSTRUCTS SAS TO USE THE MONYY7.
FORMAT FOR VALUES BEYOND NOVEMBER
16, 2016 */
7 RUN;
If you forget the "d" to use a date constant, you will get an error in lines 3-6. Line 3 uses
the special value "LOW". Without it, any date before November 16, 2013, would display as
the actual SAS numeric value. Similarly, line 6 accounts for dates in the future by using the
special value "HIGH". We tell SAS to use one of its own date formats if the date is after
November 16, 2016 by enclosing a format name in brackets after the equal sign. Without
that, there would be no format associated with SAS date values after November 16, and
only the number of days since January 1, 1960 would be displayed.
PRETTY AS A PICTURE
The second way to create your own format for your date, time, or datetime is with a picture
format. Picture formats allow you to describe what you want your date to look like. Special
formatting directives allow you to represent dates, times and datetime values, and are
case-sensitive. You will also need to use the DATATYPE= option in your PICTURE
statement. DATATYPE is DATE, TIME, or DATETIME to indicate the type of value you are
formatting. Table 1 lists the directives:
6
Example 7 shows how to use date directives to create an enhanced date display with the
day of the year, starting with the code:
1. PROC FORMAT;
2. PICTURE xdate
3. . - .z = "No Date Given"
4. LOW - HIGH = '%B %d, %Y is day %j of %Y' (DATATYPE=DATE);
5. RUN;
Unfortunately, that means you will be responsible for changing the code every week. You
can get around this by using one of the date and time automatic macro variables in SAS:
&SYSDATE; &SYSDATE9; &SYSDAY, &SYSTIME. They are set when the SAS job starts and
you cannot change them. Sample Code 4 demonstrates:
TITLE "Date of report: &SYSDATE9";
/* Since you are using a macro variable, you MUST have DOUBLE quotes around
the text */
/* Create a global macro variable called &RDATE and give it the value of
today's date formatted with the worddate32. format. Use the STRIP()
function to remove leading spaces or else you'll get an unwelcome
surprise! Use CALL SYMPUTX instead of CALL SYMPUT to remove any
trailing blanks in the macro variable. */
CALL SYMPUTX('rdate',STRIP(PUT(DATE(),worddate32.)));
RUN;
TITLE "Date of report: &rdate"; /* Don't forget DOUBLE quotes! */
Sample Code 5: Using CALL SYMPUTX() to Create a Macro Date Variable
The value of the macro variable &RDATE is "March 24, 2019", and it is left-justified, so the
title on each page will now read "Date of report: March 24, 2019". You can take this code as
written above, change the format from WORDDATE32. to whatever you need, put it into
your reports and the date in your title will automatically change each day they are run.
8
Next is another example of what you can do with custom formats and your TITLEs and
FOOTNOTEs. Once the format is created, this can also be done with a DATA step as shown
above. The first part of Sample Code 6 below creates a custom format named DEDATE using
the PICTURE statement.
1 PROC FORMAT;
2 PICTURE dedate
3 . - .z = "No Date Available" /* What if the date (datetime in this
case) is missing? */
4 LOW - HIGH = '%B %d, %Y at %I:%0M %p' (DATATYPE=DATETIME)
5 ;
6 RUN;
9
10/26/2000
09/03/1998
05/14/1967
08/25/1989
07/01/2004
03/16/2001
03/16/1971
04/03/1968
09/25/1965
To read the above file, you would use the following DATA step. Note the MMDDYY10. after
the variable name SAMPLE_DATE. This is the informat, and it tells SAS how to process the
ten characters it is reading (that's what the 10 in MMDDYY10. means.)
1. DATA read_dates;
2. INFILE "a_few_dates.txt" PAD;
3. INPUT @1 sample_date :MMDDYY10.;
4. RUN;
The first column is the value that is stored in the dataset created by the above code. Extra
columns have been added to show that value when it is displayed using two different
formats. The output is right-aligned because that is the SAS default for numeric values,
regardless of formatting.
10
1 DATA read_dates;
2 INFILE "c:\book\examples\a_few_dates.txt";
3 INPUT @1 sample_date :DDMMYY10.;
4 RUN;
At least you can see the error messages. But you didn't get an error message on every line.
What happened to the data on lines 2, 5 and 8? Let's show the result side by side with the
table from Example 8 on the right in italics.
Formatted
SAS Date Using Formatted Using SAS Date Formatted Using
Value MMDDYY10. WEEKDATE. Value MMDDYY10.
20022 10/26/2014
19061 03/09/2012 Friday, March 9, 2012 19239 09/03/2012
2690 05/14/1967
20325 08/25/2015
19730 01/07/2014 Tuesday, January 7, 2014 19905 07/01/2014
18702 03/16/2011
4092 03/16/1971
2985 03/04/1968 Monday, March 4, 1968 3015 04/03/1968
38254 09/25/2064
Example 9: When You Use an Incorrect Informat to Read Data
You can see how important it is to use the correct informat; instead of the data you expect,
you get missing values and incorrect data.
11
HOW DO I USE AN INFORMAT WHEN MY RAW DATA IS DELIMITED?
Example 8 used a column pointer (the "@1") to tell SAS where to start reading, because the
data were neatly aligned in columns throughout the flat file.
INPUT @1 sample_date :MMDDYY10.;
What do you do when you have a file that doesn't use columns, but has delimiters between
fields such as tab characters or commas? Start the informat name with a colon (":").
Example 10 will use data from a CSV file:
Smith,12DEC1950,Goal,1989
Jones,30OCT1994,Defense,
Hull,9AUG1964,Wing,2005
Oates,27AUG1962,Center,2004
As you can see, the obvious date isn't in the same column for each line. The following code
is used to read these data. The DELIMDATES dataset is then used with PROC REPORT to
produce the resulting table.
DATA delimdates;
INFILE "delim.csv" DLM=',' DSD PAD MISSOVER;
/* MISSOVER translates missing columns into missing data */
INPUT name $ birthDate :date. position $ retired;
RUN;
The bolded ":date." in line 3 tells SAS to apply the DATE. informat to the second field, since
we can't figure out where the field starts. Here's the result, again using an aliased variable
to provide a formatted version of the variable birthDate.
Birth Date
Name (SAS Date Value) Formatted Birth Date Position Year Retired
Smith -3307 Tuesday, December 12, 1950 Goal 1989
Jones 12721 Sunday, October 30, 1994 Defense
Hull 1682 Sunday, August 9, 1964 Wing 2005
Oates 969 Monday, August 27, 1962 Center 2004
Example 10: Using the Colon (:) Informat Modifier to Read Delimited Data
12
DATESTYLE= option allows you to tell SAS what it should be. Table 2 details the four
possible values, assuming the default YEARCUTOFF value of 2015:
Sets the default order as month, day, year. "06-11-15" would be translated as June
MDY
11, 2015
Sets the default order as day, month, year. "06-11-15" would be translated as
DMY
November 6, 2015
Sets the default order as year, month, day. "06-11-15" would be translated as
YMD
November 15, 2006
Sets the default value according to the LOCALE= system option. When the default
LOCALE
value for the LOCALE= system option is "English_US", this sets DATESTYLE to MDY.
(default)
Therefore, by default, "06-11-15" would be translated as June 11, 2015.
Table 2: Values for the DATESTYLE= Option
Although it may seem simple to resolve ambiguous dates using the ANYDT series of
informats, it is not foolproof. If you use them, always check the resulting data, especially if
you need to process dates with two-digit years. However, it is good programming practice
to check your datasets regardless of how simple the task of creating them may seem.
AM I READING ENOUGH?
One last issue with reading dates and times from an external file is ensuring you are reading
the correct number of characters in the field. All of the informats have a length
specification, which tells SAS how many characters to process using the informat. For
example, the MMDDYY10. informat reads ten characters and will attempt to translate it as
mm-dd-yyyy, although the exact separator between month, day, and year may vary. If you
do not use the length specification on your informat, each one has its own default length.
However, this default may not match your data.
The bottom line is that when you are reading formatted date values that have not been
stored as SAS dates, it is best to look at the data you have produced before you actually
use it.
13
function is that hours must be greater than or equal to zero, while minutes and seconds
have no restrictions on their value. HMS(7,45,80); will calculate to 27,980 seconds; to SAS,
that is exactly the same number as HMS(7,46,20). The format you use ultimately
determines the context of the time value. If you are talking about clock time, the value
returned will be MOD(result,86400). You can use the following program to test this and help
your understanding, substituting actual non-clock values for hours, minutes, and second.
The first value printed in the resulting log will be the total number of seconds; the second
value will display the number of seconds calculated to a 24-hour clock (that
MOD(value,86400) thing again.) Sample Code 7 illustrates:
DATA _NULL_;
x = HMS(hours,minutes,seconds);
PUT x= +3 'x=' x time.;
RUN;
Sample Code 7: How the HMS() Function Works
The DHMS(date,hour,minute,second); function will create a datetime value from the
component parts of date, hour, minute, and second in the same way the previous two
functions work. Again, each argument must not be missing. date can be a date literal, a
SAS date value, or a numeric variable.
14
DATA _NULL_;
datetime = "27JAN1960:12:35:00"dt;
time = "12:35:00"t;
date = "27JAN1960"d;
day_datim = DAY(datetime);
day_tm = DAY(time);
day_date = DAY(date);
PUT datetime= +3 time= +3 date= ;
PUT day_datim= +3 day_tm= +3 day_date=;
RUN;
Example 11: Using a SAS Date Function to Process the Wrong Type of Data
The first line of output shows the actual values that SAS is operating on. In the above
example, I purposely used an early datetime value (2,291,700 seconds relative to midnight,
January 1, 1960) because it gives me a non-missing value, albeit incorrect, for the day. If
you use a datetime sometime after March of 1960, SAS will give you an error message.
However, here the error message does not occur because SAS recognizes it as a datetime
value; rather it occurs because it is trying to determine a day for a value that is too large
for the function to work. Example 12 is trickier:
DATA _NULL_;
datetime = "27MAR1998:12:15:00"dt;
time = "12:15:00"t; date = "27MAR1998"d;
minit_datim = MINUTE(datetime);
minit_tm = MINUTE(time);
minit_date = MINUTE(date);
PUT datetime= +3 time= +3 date= ;
PUT minit_datim= +3 minit_tm= +3 minit_date=;
RUN;
Example 12: SAS Date/Time Functions Give Misleading Results Due to Incorrect
Context
What happened here? Isn't that the correct value for the datetime value? Not exactly.
Remember that both times and datetimes are stored in seconds since midnight, and that
times cycle every 86,400 seconds. Any value greater than 86,400 (the number of seconds
in a day) just cycles into the next day. Therefore, when you calculate minutes from the
datetime value as shown above, you may seem to get the right answer, when in reality, you
are getting the result of MOD(value,86400). So while the time extraction functions seem to
work with datetimes, it is not good practice. You can also clearly see that trying to get the
minute out of the date value gives you an incorrect result.
The YEAR() function is affected by the YEARCUTOFF option only if you have a date literal
with a two-digit year, and this can lead to unexpected results. On the other hand, if you are
extracting the year from a SAS date value, the YEARCUTOFF option will have no effect,
because the concept of two-digit year vs. four-digit year does not exist; it is simply the
number of days since January 1, 1960.
15
INTERVALS
Intervals are another way people track periods of time. SAS can allow you to manipulate
dates and times and datetimes by these intervals. SAS Intervals are very powerful, but
frequently misunderstood, especially when they are used in association with the two interval
calculation functions INTCK() and INTNX(). The INTCK() function counts the number of
intervals between two given dates, times, or datetimes, while the INTNX() function takes a
given date, time or datetime and increments it by a given number of intervals. SAS has
several standard intervals defined in it. The intervals represent periods of time such as
days, weeks, months and quarters, to name a few. Appendix 3 contains the complete list of
SAS-supplied intervals, along with the default starting point for each one. While intervals
such as "YEAR" and "MONTH" may seem self-explanatory, if you just jump right in and use
SAS intervals without reading about them first, you may not get the results you expect. A
paper containing a more in-depth discussion of SAS date intervals is listed in the references
section of this paper.
16
WHEN IS 3 MONTHS FROM TODAY?
The INTNX() function advances a given date, time or datetime by a specified number of
intervals. The syntax is:
INTNX(interval,start-date,number-of-increments,alignment);
interval is one of the SAS intervals from the previous page (again in quotes), start-date is
the starting date, and number-of-increments is how many intervals you want to change
the date. alignment will adjust the result of INTNX() relative to the interval given. It can
be 'B', 'M', or 'E' (quotes necessary) for the beginning, middle, or end of the interval,
respectively. There is also the 'S' alignment value, which will adjust the result to the same
day as given in the start-date argument. Example 13 illustrates how INTNX() and
alignment works using a sample program that adds six months to March 20, 2017. The
result is shown in bold:
DATA _NULL_;
a = INTNX('MONTH','20MAR2017'd,6);
b = INTNX('MONTH','20MAR2017'd,6,'B');
c = INTNX('MONTH','20MAR2017'd,6,'M');
d = INTNX('MONTH','20MAR2017'd,6,'E');
e = INTNX('MONTH','20MAR2017'd,6,'S');
PUT "6 months from 3/20/2017 with default alignment = " a mmddyy10.;
PUT "6 months from 3/20/2017 aligned with beginning of MONTH interval= " b
mmddyy10.;
PUT "6 months from 3/20/2017 aligned with middle of MONTH interval= " c
mmddyy10.;
PUT "6 months from 3/20/2017 aligned with end of MONTH interval= " d
mmddyy10.;
PUT "6 months from 3/20/2017 aligned with same day in MONTH interval= " e
mmddyy10.;
RUN;
17
If you do not account for this, you will be off by one unit. Another handy way to think of it is
that the YEAR.1 interval is the same as the YEAR interval.
That's it, unless the ISO 8601 datetime value includes time zones, in which case the
datetime conversion becomes:
SAS-datetime-variable = INPUT(ISO 8601-datetime-variable,E8601DZ.);
If you are using a version of SAS prior to 9.4, here's a necessary alternative:
SAS-datetime-variable = INPUT(COMPRESS(ISO 8601-datetime-variable,'-:.+ '),
B8601DT.);
Why do you need to use the COMPRESS() function? It's a solution to a problem described in
SAS Note 43337, which was fixed in SAS 9.3 TS1M1. Prior to that release, if you had a
partial ISO date value (e.g., "2011-03-20T15:30"), you would not be able to process it with
the B8601DT. informat because the default length of the informat is 19 (which is also the
minimum length allowed by SAS), but the length of the character value is 16. You cannot
specify B8601DT16. (if you try, you get an error.) Conversely, if you use the default length,
you will get the error message: "NOTE: Invalid argument to function INPUT" in your
SAS log.
There is also a major downside to using the basic (starts with 'B", not "E") ISO datetime
informats: SAS will make assumptions for partial ISO date, time, and datetime values. This
can be tricky when it comes to processing partial ISO datetime values where the date
and/or time may not be complete. SAS will not return a missing value, even if that is what
you want. SAS will substitute midnight for missing times, the first for missing days, and
January for missing months. Only if there is no year present, then the datetime value will be
set to missing. The workaround is easy. You can conditionally execute the datetime code
above by checking the ISO string for the letter 'T'. If it is present, then the code will provide
you with a datetime value. If not, then set it to missing, and calculate the date as follows:
SAS-date-variable = DATEPART(INPUT(COMPRESS(ISO 8601-date-variable,'-:. ')
,B8601DT.));
It is even easier to represent a SAS datetime value using the ISO 8601 standard: you
simply use one of the formats in Table 4:
18
E8601DA. Represents a SAS date value in the format yyyy-mm-dd
E8601DT. Represents a SAS datetime value in the format yyyy-mm-ddThh:mm:ss.ffffff
(ffffff represents fractional seconds)
E8601DZ. Represents a SAS datetime value in the format yyyy-mm-ddThh:mm:ss+|-
hhmm
E8601TM. Represents a SAS time value in the format hh:mm:ss.ffffff (ffffff represents
fractional seconds)
Table 4: ISO 8601 Formats
EXCEL
We cannot overlook the potential for incorrect results whenever you convert from Microsoft
Excel into SAS or vice versa. Like SAS, Excel counts days from a fixed reference point, and
it uses January 1, 1900. Excel keeps track of times as decimal fractions where you can
multiply by 24 (hours) to get the clock time of the day. Six a.m. is a quarter of a day (.25);
noon is exactly one-half day; and so on. A datetime value in Excel is the sum of the day and
the time value. For example, September 5, 2014 is 41887 to Excel, so noon on September
5, 2014 would be 41887.5. Unfortunately, this also means Excel does not keep track of time
as a separate entity from dates. A period of more than 24 hours is translated by Excel into
more than 1 day. As an example, if you have a period of 36 hours (1.5 in Excel), Excel will
happily convert this into noon of January 2, 1900 for you.
Exchanging historical dates between SAS and Excel is a definite problem. While Excel uses a
zero point of January 1, 1900, unlike SAS, it cannot count backwards from zero. Therefore,
any date prior to 1900 in Excel is represented as text, not as an Excel date value. What do
you do if you have dates before January 1, 1900, to convert? Going to Excel, you will have
to store them as character strings, and you will not be able to use any of the Excel math or
date functions on them. On the other hand, if you are going from Excel to SAS, then you
can use an INPUT statement and a DATA step to process a CSV file. If you use the IMPORT
wizard and/or procedure, you may get the dreaded dot, or worse. SAS may decide the
entire column is a character variable, leaving you with text where your date values should
be. Unless you have historical dates, the IMPORT procedure or SAS/ACCESS ® will convert
dates, times and datetimes for you, and it is fairly reliable. However, when you are
importing into SAS from any other software or database, always test your dates, times and
datetimes, because different software uses different representations of dates, times, and
datetimes, and the default conversion is not foolproof.
SUMMARY
The way SAS handles dates, times, and datetimes allows for a great deal of flexibility in the
display, manipulation, and computation of these important data. However, that flexibility
can also make it easy to make mistakes. Dates, times, and datetimes are stored as
numbers in SAS. Dates are counted in days from a zero point of January 1, 1960. Times are
counted in seconds from a zero point of midnight of the current day, and datetimes are
counted in seconds since midnight, January 1, 1960. Each day that passes increments the
day counter by one, and each second that passes increments the time and datetime
counters by one.
SAS dates and times are displayed by using formats. There are many SAS-supplied formats
and each displays a date, time, or a datetime value as something we can recognize. Date
formats are specific to date values, time formats are specific to time values, and datetime
formats are specific to datetime values. If you use the wrong type of format, your display
will be incorrect. If none of the SAS-supplied formats meets your needs, you can create
19
your own, either by defining the display to correlate with certain values, or by defining the
display with a template using symbolic directives. You can use formats to customize titles
and footnotes in your output with date and time information.
We use informats to translate dates and times as we know them into the values recognized
by SAS. As with formats, there are many SAS-supplied informats designed to translate
standard alphanumeric or numeric representations of dates into SAS date, time, or datetime
values. You need to specify the number of characters to be processed by the informat, and
you should use the correct informat for the alphanumeric characters you are processing. If
you do not know the format of the alphanumeric characters you will be processing, you can
use the ANYDATE informats, but be prepared for your job to take longer, and always check
your results.
You may extract the individual components of a SAS date, time, or datetime value, such as
month number, day or hour. However, you need to make sure you do not try to extract a
month from a time, or an hour from a date. You may also create a SAS date, time, or
datetime value from individual components. Whenever you do this, remember that all of the
arguments to the MDY(), HMS(), and DHMS() functions must be non-missing for these
functions to work.
It is frequently useful to refer to periods of time by familiar references such as year or
quarter. SAS uses intervals to handle this. There are several such standard periods defined
in SAS, and two functions that use them, INTCK() and INTNX(). The most important thing
to remember about these functions, is that by default, they use SAS intervals by measuring
the intervals from the start point of the interval, not the dates you supply.
There are many more uses of SAS dates, more functions relating to dates and times and
datetimes, and more capabilities of intervals than have been shown in this paper. This
introduction provides an overview of handling and manipulating dates and times in SAS, and
addresses a large percentage of date- and time-related tasks. Even if you need something
that has not been covered in this paper, the tools SAS provides to handle dates and times
should allow you to accomplish your tasks.
REFERENCES
Morgan, Derek P. 2014. The Essential Guide to SAS® Dates and Times, Second Edition.
Cary, NC: SAS Institute Inc.
Morgan, Derek, 2017, “Demystifying Intervals.” Proceedings of MWSUG 2017. Available at:
http://www.mwsug.org/proceedings/2017/BB/MWSUG-2017-BB042.pdf
CONTACT INFORMATION:
Comments and questions welcome to:
Derek Morgan
E-mail: [email protected]
SAS and all other SAS Institute Inc. product or service names are registered trademarks or
trademarks of SAS Institute Inc. in the USA and other countries. ® indicates USA
registration. Other brand and product names are trademarks of their respective companies.
20
APPENDIX 1: HOW DO I?
1) Convert a character date into a valid SAS date?
This is one of the most frequently asked questions, usually occurring when moving data
from other software into SAS. The example uses the ANYDTDTE. informat, but it is more
efficient to use one of the specific date informats.
SAS-date-variable = INPUT(character_date,ANYDTDTE.);
With a little more effort, you can label worksheet tabs in a workbook using this method.
3) Turn my date from a number into something I understand?
Use a format. In a DATA step or procedure, the code would be:
FORMAT date mmddyy10.;
In SQL:
SELECT date FORMAT=mmddyy10.
4) Turn my asterisks into a date? I used a date format like you said.
This generally means the value you are trying to display is too big to display with the format
you are using. With dates, this happens most often when you use a date format to display a
datetime value. The log segment below demonstrates:
1 DATA _NULL_;
2 datetime=1682340217;
3 PUT "Datetime value formatted with DATE9. format " datetime date9.;
4 PUT "Datetime value formatted with DATETIME19. format " datetime
datetime19.;
5 RUN;
Datetime value formatted with DATE9. format *********
Datetime value formatted with DATETIME19. format 23APR2013:12:43:37
You may also see this problem with custom picture formats. If you are using a custom
format to display your dates and you get asterisks, the format length you specified isn’t
long enough. If you didn’t specify a format length, then it’s set to the default length SAS
automatically calculated for your custom format based on the maximum number of
characters between quotes. Since date directives only occupy two spaces, it’s probably
shorter than you need. To fix it, either specify a length when you use the format like this:
FORMAT mydate myformat14.;
21
Or use the DEFAULT= option when you create the custom format:
PICTURE myformat (DEFAULT=14) /* Display date as full month name and 4-
digit year */
LOW-HIGH = '%B %Y' (DATATYPE=DATE)
The maximum number of letters (English spelling) in a full month name is nine. You also
count the space and four digits for the year; therefore, a minimum of fourteen characters is
needed to display the date as full month name and year.
5) Figure out how many days have elapsed between two SAS dates?
Since SAS keeps track of dates as number of days relative to January 1, 1960, subtraction
is the quickest method. This also works for times and datetimes, but you will get the result
in seconds.
how_many_days = date2 - date1;
6) Create a datetime from month, day and year when I don't have a time?
This takes two steps if you don't already have a SAS date:
SAS_date = MDY(month,day,year);
SAS_datetime_value = DHMS(SAS_date,0,0,0);
If you have a SAS date and SAS time available you can just use both in the DHMS()
function. SAS times are stored in seconds, so there's no need to convert into hours,
minutes, seconds, but you do need zeroes as placeholders for hours and minutes:
SAS_datetime_value = DHMS(SAS_date,0,0,SAS_time);
22
APPENDIX 2: HANDY CUSTOM DATE FORMATS AND INFORMATS
Here are some of the most common date formats and informats I've needed. I create a
permanent format library for my projects, so I don't have to worry about coding these every
time I need one of them.
PROC FORMAT LIB=LIBRARY;
VALUE ctday /* Translate weekday numbers (like from the WEEKDAY() function)
Into weekday names
*/
1 = "Sunday"
2 = "Monday"
3 = "Tuesday"
4 = "Wednesday"
5 = "Thursday"
6 = "Friday"
7 = "Saturday"
;;;;
PICTURE monthyr (DEFAULT=14) /* Display date as full month name and 4-digit
year */
LOW-HIGH = '%B %Y' (DATATYPE=DATE)
;;;;
RUN;
23
APPENDIX 3: SAS INTERVALS AND THEIR STARTING POINTS
Default
Starting
Interval Name Definition Point
24
Default
Starting
Interval Name Definition Point
1st of each
DTMONTH Monthly intervals used with datetime values
month
1-Jan
1-Apr
DTQTR Quarterly (three-month) intervals used with datetime values
1-Jul
1-Oct
1-Jan
DTSEMIYEAR Semiannual (six-month) intervals used with datetime values
1-Jul
DTYEAR Yearly intervals used with datetime values 1-Jan
DTSECOND Second intervals used with datetime values Seconds
DTMINUTE Minute intervals used with datetime values Minutes
DTHOUR Hour intervals used with datetime values Hours
25