« LoadRunner - Date Handling (3 of 3) - Powershell | Main | LoadRunner - Date Handling (1 of 3) - lr_save_datetime »
Friday
Aug242012

LoadRunner - Date Handling (2 of 3) - C Date/Time Functions

If you are using LoadRunner, you've probably encountered date and time values in your scripts. These values should be evaluated and dealt with.

In Part 1 of this series, we explored using lr_save_datetime. Unfortunately, this is insufficient for the following type of problems.

 Tougher Questions And Answers

What if the date could be any date?  Given the date format, how would you safely add/subtract date/time elements? 

  • 21-Aug-2012 07:33:00, add 1 minute
  • 31-Dec-2011 23:59:59, add 1 minute
  • 24-Feb-2009 12:34:56, add 50 hours
  • 01-Sep-2012 09:00:00, subtract 100 days

Let's solve the problem using C date/time functions.

 Initial Thoughts

At first I looked at lr_save_datetime.  No help there.  I thought I would have to convert the date string to a Julian Date or other value, add/subtract appropriately and convert back using the date format.  This proved to be too complicated.

I did not want to parse the date and manually manipulate the values.  This would require a lot of logic to handle the minute, hour, day, month and leap year scenarios.

 C Date/Time Functions

This solution assumes the en_US locale.  If another locale is used, change the related values or expand the solution to handle.  There are Windows functions available to get the appropriate locale information.

 Goal

  • Create a function to hide the implementation details.
  • Easy, straightforward, simple solution.
  • Handle the tough date manipulations mentioned earlier.

Example Code

long epoch;
char *strServerDate = "17-Aug-2012 06:49:41";
char *strServerDate2 = "2012-06-04 10:52:32";
ChangeDateTime (strServerDate, "pServerNewDate", "%d-%b-%Y %H:%M:%S", 'Y', 70);
lr_output_message ("Add 70 Years = %s", lr_eval_string ("{pServerNewDate}"));

 Breakdown of Tasks

  • Get the current date/time.  This will be used if any date/time values are missing.
  • Parse the given date using the given format.  Parsed information will be stored in a 'tm' structure.
  • Increment/decrement the desired date/time part.
  • Build the new date given the format.

 Get the Current Date/Time

Sample Code

// From LoadRunner Help (Converting Time to Local Time)

typedef long time_t;
struct tm
{
	int tm_sec;    // seconds after the minute (0-59)
	int tm_min;    // minutes after the hour (0-59)
	int tm_hour;   // hours since midnight (0-23)
	int tm_mday;   // day of the month (1-31)
	int tm_mon;    // months since January (0-11)
	int tm_year;   // years since 1900
	int tm_wday;   // days since Sunday (0-6)
	int tm_yday;   // days since January 1 (0-365)
	int tm_isdst;  // Daylight Saving Time flag (=0 not in effect, >0 if in effect)
};

time_t t;
struct tm *tmNewDate;
time (&t);
tmNewDate = (struct tm *) localtime (&t);
// tmNewDate contains the current date/time information, parsed into its tm components

 Parse the Date

Now we need to parse the date we have been given, using the supplied format.  Any found information will be written into the tmNewDate structure.  The final result will be tmNewDate will have the passed in date's information, with omitted information defaulting to the current date/time.

This is accomplished by traversing the date and format strings in parallel.  Keep track of both string's current position. When a format code is identified, extract the corresponding date value from the passed in date.

Sample Code

	int i = 0;
	int j = 0;
	char cFormatCode;
	int iLen;
	int iFormatLen;

	// -------------------------------------------------------------------------

	iLen = strlen (strFormat);
	while (j<iLen)
	{
		if (strFormat[j++] == '%')
		{
			// -----------------------------------------------------------------
			// Assume format codes are 2 characters in length, with the first
			// character being the % character.  The format code tells us how
			// many characters it will represent in the date.
			// -----------------------------------------------------------------
			
			cFormatCode = (char)strFormat[j++];
			iFormatLen = GetFormatLength(cFormatCode);

			strncpy (strBuffer, &strDate[i], iFormatLen);
			strBuffer[iFormatLen] = 0;
			i = i + iFormatLen;

			switch (cFormatCode)
			{
				case 'Y': tmNewDate->tm_year = atoi (strBuffer) - 1900;     break;
				case 'm': tmNewDate->tm_mon  = atoi (strBuffer) - 1;        break;
				case 'b': tmNewDate->tm_mon  = GetMonthPosition(strBuffer); break;
				case 'd': tmNewDate->tm_mday = atoi (strBuffer);            break;
				case 'H': tmNewDate->tm_hour = atoi (strBuffer);            break;
				case 'M': tmNewDate->tm_min  = atoi (strBuffer);            break;
				case 'S': tmNewDate->tm_sec  = atoi (strBuffer);            break;

				default:
					lr_error_message ("Unknown format code in date format : %c", cFormatCode);
					lr_save_string (strDate, strNewDateParam);
					return -1;
			}
		}
		else
		{
			// -----------------------------------------------------------------
			// Not a format code, plain text, nothing to do.
			// -----------------------------------------------------------------

			i++;    // Move to next position in the date.
		}
	}

 Increment/Decrement Date

Now that we have the tNewDate representing the passed in date, we need to manipulate it based on the passed in change value.

Sample Code

	switch (charType)
	{
		case 'Y': tmNewDate->tm_year = tmNewDate->tm_year + iChange; break;
		case 'm': tmNewDate->tm_mon  = tmNewDate->tm_mon  + iChange; break;
		case 'b': tmNewDate->tm_mon  = tmNewDate->tm_mon  + iChange; break;
		case 'd': tmNewDate->tm_mday = tmNewDate->tm_mday + iChange; break;
		case 'H': tmNewDate->tm_hour = tmNewDate->tm_hour + iChange; break;
		case 'M': tmNewDate->tm_min  = tmNewDate->tm_min  + iChange; break;
		case 'S': tmNewDate->tm_sec  = tmNewDate->tm_sec  + iChange; break;

		default:
			lr_error_message ("Unknown date code for change : %c", charType);
			lr_save_string (strDate, strNewDateParam);
			return -1;
	}

 Build the New Date

Now that the modification is complete, we need to build the new date given the passed in format.

Sample Code

	t = mktime (tmNewDate);
	strftime (strBuffer, 128, strFormat, tmNewDate);
	lr_save_string (strBuffer, strNewDateParam);

 Complete Code

Let's put it all together into a runnable script with additional comments.

Complete Code

char strBuffer[128];
typedef long time_t;
struct tm
{
	int tm_sec;    // seconds after the minute (0-59)
	int tm_min;    // minutes after the hour (0-59)
	int tm_hour;   // hours since midnight (0-23)
	int tm_mday;   // day of the month (1-31)
	int tm_mon;    // months since January (0-11)
	int tm_year;   // years since 1900
	int tm_wday;   // days since Sunday (0-6)
	int tm_yday;   // days since January 1 (0-365)
	int tm_isdst;  // Daylight Saving Time flag (=0 not in effect, >0 if in effect)
};

Action()
{
	// -------------------------------------------------------------------------
	// Date from server simulation
	// -------------------------------------------------------------------------

	long epoch;
	char *strServerDate = "17-Aug-2012 06:49:41";
	char *strServerDate2 = "2012-06-04 10:52:32";
	lr_output_message ("Server Date = %s", strServerDate);
	lr_output_message ("Server Date 2 = %s", strServerDate2);

	// -------------------------------------------------------------------------
	// Add 1 minute to the returned server date.
	// -------------------------------------------------------------------------

	lr_output_message (" ");

	ChangeDateTime (strServerDate, "pServerNewDate", "%d-%b-%Y %H:%M:%S", 'M', 1);
	lr_output_message ("Add 1 Minute = %s", lr_eval_string ("{pServerNewDate}"));

	// -------------------------------------------------------------------------
	// Add 70 to each of the date/time components.  The mktime function will
	// automatically recalculate to the correct date/time.
	// -------------------------------------------------------------------------

	lr_output_message (" ");

	ChangeDateTime (strServerDate, "pServerNewDate", "%d-%b-%Y %H:%M:%S", 'Y', 70);
	lr_output_message ("Add 70 Years   = %s", lr_eval_string ("{pServerNewDate}"));

	ChangeDateTime (strServerDate, "pServerNewDate", "%d-%b-%Y %H:%M:%S", 'b', 70);
	lr_output_message ("Add 70 Months  = %s", lr_eval_string ("{pServerNewDate}"));

	ChangeDateTime (strServerDate, "pServerNewDate", "%d-%b-%Y %H:%M:%S", 'd', 70);
	lr_output_message ("Add 70 Days    = %s", lr_eval_string ("{pServerNewDate}"));

	ChangeDateTime (strServerDate, "pServerNewDate", "%d-%b-%Y %H:%M:%S", 'H', 70);
	lr_output_message ("Add 70 Hours   = %s", lr_eval_string ("{pServerNewDate}"));

	ChangeDateTime (strServerDate, "pServerNewDate", "%d-%b-%Y %H:%M:%S", 'M', 70);
	lr_output_message ("Add 70 Minutes = %s", lr_eval_string ("{pServerNewDate}"));

	ChangeDateTime (strServerDate, "pServerNewDate", "%d-%b-%Y %H:%M:%S", 'S', 70);
	lr_output_message ("Add 70 Seconds = %s", lr_eval_string ("{pServerNewDate}"));

	// -------------------------------------------------------------------------
	// Convert the given date to its epoch value.  Epoch is the number of
	// seconds since 01/01/1970 00:00:00.  
	// -------------------------------------------------------------------------

	epoch = ChangeDateTime (strServerDate2, "pServerNewDate", "%Y-%m-%d %H:%M:%S", 'S',0);
	lr_output_message ("Epoch of %s = %ld", lr_eval_string ("{pServerNewDate}"), epoch);

	// -------------------------------------------------------------------------

	return 0;
}

int GetFormatLength(char cCode)
{
	if (cCode == 'd') { return 2; }
	if (cCode == 'm') { return 2; }
	if (cCode == 'b') { return 3; }
	if (cCode == 'Y') { return 4; }
	if (cCode == 'H') { return 2; }
	if (cCode == 'M') { return 2; }
	if (cCode == 'S') { return 2; }

	return 0;
}

int GetMonthPosition(char *strMonth)
{
	if (strcmp (strMonth, "Jan") == 0) { return  0; }
	if (strcmp (strMonth, "Feb") == 0) { return  1; }
	if (strcmp (strMonth, "Mar") == 0) { return  2; }
	if (strcmp (strMonth, "Apr") == 0) { return  3; }
	if (strcmp (strMonth, "May") == 0) { return  4; }
	if (strcmp (strMonth, "Jun") == 0) { return  5; }
	if (strcmp (strMonth, "Jul") == 0) { return  6; }
	if (strcmp (strMonth, "Aug") == 0) { return  7; }
	if (strcmp (strMonth, "Sep") == 0) { return  8; }
	if (strcmp (strMonth, "Oct") == 0) { return  9; }
	if (strcmp (strMonth, "Nov") == 0) { return 10; }
	if (strcmp (strMonth, "Dec") == 0) { return 11; }

	return 0;
}

long ChangeDateTime (char *strDate, char *strNewDateParam, 
                     char *strFormat, char charType, int iChange)
{
	time_t t;
	struct tm *tmNewDate;
	int i = 0;
	int j = 0;
	char cFormatCode;
	int iLen;
	int iFormatLen;

	// -------------------------------------------------------------------------

	iLen = strlen (strFormat);

	// -------------------------------------------------------------------------
	// The current date/time will be used if any date/time values are missing in
	// the strDate string.
	// -------------------------------------------------------------------------
	 
	time (&t);
	tmNewDate = (struct tm *) localtime (&t);

	// -------------------------------------------------------------------------
	// Fill the tmNewDate structure with the date given.
	// Parse the date based on the date format.  For each format code found, set
	// the corresponding tmNewDate value.
	// -------------------------------------------------------------------------

	while (j<iLen)
	{
		if (strFormat[j++] == '%')
		{
			// -----------------------------------------------------------------
			// Assume format codes are 2 characters in length, with the first
			// character being the % character.  The format code tells us how
			// many characters it will represent in the date.
			// -----------------------------------------------------------------
			
			cFormatCode = (char)strFormat[j++];
			iFormatLen = GetFormatLength(cFormatCode);

			strncpy (strBuffer, &strDate[i], iFormatLen);
			strBuffer[iFormatLen] = 0;
			i = i + iFormatLen;

			switch (cFormatCode)
			{
				case 'Y': tmNewDate->tm_year = atoi (strBuffer) - 1900;     break;
				case 'm': tmNewDate->tm_mon  = atoi (strBuffer) - 1;        break;
				case 'b': tmNewDate->tm_mon  = GetMonthPosition(strBuffer); break;
				case 'd': tmNewDate->tm_mday = atoi (strBuffer);            break;
				case 'H': tmNewDate->tm_hour = atoi (strBuffer);            break;
				case 'M': tmNewDate->tm_min  = atoi (strBuffer);            break;
				case 'S': tmNewDate->tm_sec  = atoi (strBuffer);            break;

				default:
					lr_error_message ("Unknown date format code : %c", cFormatCode);
					lr_save_string (strDate, strNewDateParam);
					return -1;
			}
		}
		else
		{
			// -----------------------------------------------------------------
			// Not a format code, plain text, nothing to do.
			// -----------------------------------------------------------------

			i++;    // Move to next position in the date.
		}
	}

	// -------------------------------------------------------------------------
	// Change the requested date/time part.
	// -------------------------------------------------------------------------

	switch (charType)
	{
		case 'Y': tmNewDate->tm_year = tmNewDate->tm_year + iChange; break;
		case 'm': tmNewDate->tm_mon  = tmNewDate->tm_mon  + iChange; break;
		case 'b': tmNewDate->tm_mon  = tmNewDate->tm_mon  + iChange; break;
		case 'd': tmNewDate->tm_mday = tmNewDate->tm_mday + iChange; break;
		case 'H': tmNewDate->tm_hour = tmNewDate->tm_hour + iChange; break;
		case 'M': tmNewDate->tm_min  = tmNewDate->tm_min  + iChange; break;
		case 'S': tmNewDate->tm_sec  = tmNewDate->tm_sec  + iChange; break;

		default:
			lr_error_message ("Unknown date code for change : %c", charType);
			lr_save_string (strDate, strNewDateParam);
			return -1;
	}

	// -------------------------------------------------------------------------
	// Call mktime to generate the resulting date, format and save to parameter.
	// -------------------------------------------------------------------------

	t = mktime (tmNewDate);
	strftime (strBuffer, 128, strFormat, tmNewDate);
	lr_save_string (strBuffer, strNewDateParam);

	// -------------------------------------------------------------------------

	return (long) t;
}

 Moving Forward

  • Add additional format codes (LoadRunner help lists 22).
  • Format 'A' (weekday) and 'B' (month) have variable lengths.  Instead of using a specific length, parse the string until you get a match, tracking its length.
  • Add more error checking.
  • Handle different locales, dynamically get locale information.
  • Change the output date string by supplying a different date format string.

In Part 3 we will look at using Powershell to solve our date/time calculations.

Reader Comments (13)

Thanks! You have provided some great information.

I am working on some LoadRunner code where I need to take a date that is returned from the server in the format "2012-06-14 10:52:32.0 CDT" and convert it to the epoch format "1339689152000" to pass it back to the server. Any ideas on how I can accomplish this?

October 15, 2012 | Unregistered CommenterSM

SM, thanks for the comment! I have updated the example above to return the epoch time for the passed in date. In summary, this is what I did:

1. Created a new date string to represent the date returned by the server. I truncated off the milliseconds and time zone.
2. Since you did not want to change the date, the ChangeDateTime function was passed in a zero for the seconds to add.
3. Added the 'm' format code in a few places.
4. Changed ChangeDateTime to return a long instead of an int.
5. Captured the return of mktime. This returns the number of seconds since 01/01/1970 00:00:00.
6. Return the captured epoch time.

October 15, 2012 | Unregistered CommenterEdward Leno

Edward, thanks so much for providing the code. I was able to incorporate your code into my LoadRunner script and with a few tweaks, I got it working successfully. Thanks again!

October 17, 2012 | Unregistered CommenterSM

hi Iam using Load runner 11 version
i have to pass future date to server and this should be in milliseconds of 13 digits.and even 13 digits should be type numeric not string type

January 3, 2013 | Unregistered Commenterlokesh

I need that struct tm contains also miliseconds and to print them as well in the format in
strftime (tmp, 128, "%Y-%m-%dT%H:%M:%S.0000000+02:00", mydatestructure);
instead of the 0000000, is that possible?

Thanks in advance1

April 10, 2013 | Unregistered CommenterItamar
April 10, 2013 | Registered CommenterEdward Leno

Hi, thank you for your functions first of all... I find when I want to increment Y only by 1, it is increasing month also... any comments...?

//inceptiondate = 04/30/2013
ChangeDateTime (lr_eval_string ("{inceptiondate}"), "pServerNewDate", "%m/%d/%Y", 'Y', 1);
lr_output_message ("Add 1 Year = %s", lr_eval_string ("{pServerNewDate}"));

output: Notify: Parameter Substitution: parameter "pServerNewDate" = "05/30/2014"

April 30, 2013 | Unregistered Commentersri

Sri,

Thanks for pointing this out. I have fixed the above code to work properly. The issue is that the month is expected to be from 0 to 11. In total, 2 lines were changed:

case 'm': tmNewDate->tm_mon = atoi (strBuffer) - 1; break; // added the -1

case 'm': tmNewDate->tm_mon = tmNewDate->tm_mon + iChange; break; // removed a -1

April 30, 2013 | Registered CommenterEdward Leno

Thank you Ed, worked fine for me... will post back if any issues for any formats in future.. appreciate your time and your site...

May 1, 2013 | Unregistered Commentersri

hi.. I have an application where the data needs to be shared at run-time between the scripts. And my applicaiton needs a huge load for stress testing. Now, i am looking at the altenatives for the VTS and mySQL where there are certain limitations on the connections prospective. I noted a max 700 connections with mySQL as well the establish connection taking lot of time. Here, i read the SUID documents and interested in getting my hands dirty trying it. However, would like to know the limitaions on the load prospective. PLEase let me know if anyone has observed any load limitations while using this above 4000 users.

Thanks in advance for your time and help!
~Siva

May 2, 2013 | Unregistered CommenterSiva

Hi,
Thanks for this its a great resource - I have a 48 hr soak that needs to calculate the next 6am in each iteration. So if the iteration runs before 6am the next 6am is 6am that day, if the iteration runs after 6am (say at midday) the next 6am will be 6am on the following day.
I've tried using time(&t), dividing by 86400(seconds in a day) and % (modding) the results - but its not getting me the results I need
(NB I'll also need to account for daylight saving in the UK)

Any views on how to achieve this ...?

August 1, 2013 | Unregistered Commenteroldnich

I am working on Load Runner 9.0 and practicing date/time parameter. I want to parameterize the arrival date (on each iterations) with offset of 1 and the return date with offset of 2 but when I ran, LR takes SYS DATE instead of the date which was given as input while recording. Also I observed that LR takes only the parameterized date (e.g. If I select the date format as %m%d%y (which shows the SYS DATE as sample date) and opt to choose 2 iterations, it takes this date (SYS DATE) or offset of this date in both iterations. Please let me know it is right or not and explain me about date/time parameter explicitly.

Thanks in Advance,
Manjukumar

February 16, 2014 | Unregistered CommenterManjukumar

Thanks for the great code written above. I have a scenario where i captured the Start Date( not the current Date) and i have to increment the date for 2 weeks. for Ex: Date captured is 2014-03-10 and the end date captured is 2014-03-23. Now i need all the dates in between 10 March 2014 to 23 March 2014. i..e. i need all the dates from 10,11,12,..... 23.
We can use date functions but increment won't happen and what if in the middle of those if month ends? i.e 31 march comes in the middle of those dates? how to capture such value and increment?
Request you to Please suggest me with an answer

May 8, 2014 | Unregistered CommenterSouji

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>