Calculating the difference between two dates or timestamps

Let’s say that you have to determine the difference between two dates (represented as strings) and express the result in seconds (which could be positive or negative). How would you do this in VuGen?

Being a smart person who is not interested in reinventing the wheel, you would look first to the functions that are available to you in VuGen.

You could read the values from your time/date string using sscanf, which extracts values from a formatted string.

1
sscanf("02/10/2009 14:49:41", "%d/%d/%d %d:%d:%d", month, day, year, hour, min, sec);

You could then save these values to a special “calendar time” structure defined in the time.h standard C library.

1
2
3
4
5
6
7
8
9
10
11
  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 savings time flag 
  };

Once the date/timestamp is in the required format, it could be easily converted to a Unix timestamp by using the mktime() function, which is also in time.h.

Here is the example code…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
Action()
{
  // The "calendar time" structure has the following members
  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 savings time flag 
  }; 
 
  int rc; // return code
  struct tm date1;
  struct tm date2;
  long time_difference; // the number of time ticks (seconds) that separate date1 and date2.
  int days, hours, minutes, seconds;
 
  // Save example dates to a parameter. In real life, you might
  // capture these values using web_reg_save_param or similar.
  // date format: mm/dd/yyyy hh:mm:ss
  lr_save_string("02/10/2009 14:49:41", "Param_Date1");
  lr_save_string("02/11/2009 15:50:42", "Param_Date2");
 
  // Read the values from the string into the date variables
  rc = sscanf(lr_eval_string("{Param_Date1}"), "%d/%d/%d %d:%d:%d", &date1.tm_mon, &date1.tm_mday, &date1.tm_year, &date1.tm_hour, &date1.tm_min, &date1.tm_sec);
  if (rc != 6) {
      lr_error_message("Problem reading date format. Expected 6 values in string, but found %d", rc);
      lr_abort();
  }
 
  // As the "calendar time" structure defines tm_year as "years since 1900" and 
  // tm_mon as "months since January", we must "fix" these values in the structure.
  date1.tm_mon = date1.tm_mon - 1;
  date1.tm_year = date1.tm_year - 1900;
 
 
  // Repeat the above steps for Date2
  rc = sscanf(lr_eval_string("{Param_Date2}"), "%d/%d/%d %d:%d:%d", &date2.tm_mon, &date2.tm_mday, &date2.tm_year, &date2.tm_hour, &date2.tm_min, &date2.tm_sec);
  if (rc != 6) {
      lr_error_message("Problem reading date format. Expected 6 values in string, but found %d", rc);
      lr_abort();
  }
  date2.tm_mon = date2.tm_mon - 1;
  date2.tm_year = date2.tm_year - 1900;
 
  // Use mktime() to convert the "calendar time" structure to time ticks.
  // Even though this function is not in the VuGen function reference, it is still available to use.
  // Note that Unix time ticks are defined between 1/Jan/1970 and 
  // 19/Jan/2038, so odd things may happen with dates outside these ranges.
  time_difference = mktime(&date2) - mktime(&date1);
  lr_output_message("Total number of seconds difference: %d", time_difference);
 
  // Calculate time difference in days, hours, minutes and seconds.
  days = time_difference/86400;
  time_difference = time_difference - (days * 86400);
  hours = time_difference/3600;
  time_difference = time_difference - (hours * 3600);
  minutes = time_difference/60;
  time_difference = time_difference - (minutes * 60);
  seconds = time_difference;
  lr_output_message("Days: %d, Hours: %d, Minutes: %d, Seconds: %d", days, hours, minutes, seconds);
 
  return 0;
}

UPDATE: Calculating the difference between timestamps in milliseconds

Someone has asked how they calculate the difference between two timestamps that are in milliseconds like those that are returned by the JavaScript getTime() method, or the LoadRunner web_save_timestamp_param() function.

You would naturally think that you should be able to convert the timestamps to a number, and then just subtract them, but doing this, you run into a limitation of VuGen.

1
2
3
4
lr_output_message("The size of a long in VuGen is %d bytes", sizeof(unsigned long));
lr_output_message("The size of an int in VuGen is %d bytes", sizeof(unsigned int));
// Action.c(3): The size of a long in VuGen is 4 bytes
// Action.c(4): The size of an int in VuGen is 4 bytes

The size of both int and long is 32 bits, so the largest number they can store is 4294967295 (2n-1). Unfortunately this is narrower than a timestamp in milliseconds e.g. 1234567890123 (Friday the 13th, 2009 at 11:31:30pm and 123 milliseconds UTC), so trying to store this value inside an int will cause an overflow.

However, using a double will not cause an overflow, so the code looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
double atof (const char *string); // must explicitly declare functions which do not return an int
Action()
{
  char* start_time = "1234567890123"; // Friday the 13th, 2009 at 11:31:30pm and 123 milliseconds UTC
  double time_difference;
  web_save_timestamp_param("TimeNow", LAST);
 
  time_difference = atof(lr_eval_string("{TimeNow}")) - atof(start_time);
 
  // odd results if time_difference is greater than 2,147,483,647 (max value for signed int)
  lr_output_message("Time difference: %d milliseconds", (int)time_difference);
 
  return 0;
}

8 comments

Error 266 type error in argument 1 to `_mktime32′; found `pointer to struct tm’ expected `pointer to struct tm’ Action.c am getting this error while using the first code that you provided. on line time_difference = mktime(&date2) – mktime(&date1);

This looks like the right topic for what I had in mind
I was thinking if there’s any way to use “time.h” from the C library, because I wanted to use its clock() function.
What I wanted to do is check the execution time of the script, and extract different values for triggering stuff
All that without stopping the timer, like the LR functions do

So, I included the header file, and another one it was asking for (cdefs.h), and now at compile-time I get all sorts of errors, apparently LR doesn’t recognize everything it’s written in those headers

Any ideas?

Hi,

Thank you very much for this. I have now (with a little modification) created a script which will read & get the dates from a .csv file instead of getting the values declared directly in VuGen. The advantage is that we can put in an entire list of working dates extraced by excel and read the file. So this takes care of the problem of no of years.We can have the list as long as we want.( I have put it for 10 yrs.)

Many Thanks once again.

Anustup Ray

Stuart Moncrieff
Stuart Moncrieff

Happy to help. Good luck with your load testing. :)

Cheers,
Stuart.

Hi Thanks for your reply.

I cannot really use the working days option because I cant use the date time parameter. The problem being that this parameter will allow me to look at the present time and offset it with a given number of days or working days.

What was really looking for is a function where we can get the number of working days from any given date range. I have worked out the logic for any date range greater than or equal to 7 days but the problem is with when the difference is less than 7 days. There can be 1 Saturday only, one Sunday only, or a Saturday and a Sunday. The only way this can be done is if can find out the weekdays for those 6 days and then negate the Saturdays and Sundays.

Stuart Moncrieff
Stuart Moncrieff

So, you are really looking for a C function just like the NETWORKDAYS(start_date, end_date, holidays) function that is available in Excel.

I had a quick look for available libraries and couldn’t find anything, so I whipped up a quick and dirty function that should be good enough, providing you only want to calculate the number of working days over a small number of years.

Here is the function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
Action()
{
	lr_output_message("Number of working days between the dates is: %d", number_of_working_days("2009-06-03", "2009-09-04"));
	return 0;
}
 
 
/* 
Returns the number of working days between two dates.
Arguments
 - start_date: Date to calculate from in format yyyy-mm-dd. E.g. 2009-0
 - end_date: End date in format yyy-mm-dd. 
Returns number of days on success. On failure, function will raise lr_error_message and return -1.
BUGS: if you use an invalid date like 2009-02-31, then this function will return a number of working days as if it were a real date.
*/
int number_of_working_days(const char* start_date, const char* end_date) {
  /* This is a list of working days (Monday-Friday) excluding Victorian public holidays (taken from 
       http://www.vic.gov.au/Victorian-Public-Holiday-Dates.html). It is easiest to generate this list
       yourself using Excel and the WEEKDAY function.
     Note that declaring working_days[][] = {whatever} will work in some compilers, but not in VuGen. */
  const char working_days[][11] = {
    "2009-01-02", "2009-01-05", "2009-01-06", "2009-01-07", "2009-01-08", "2009-01-09", "2009-01-12", "2009-01-13", "2009-01-14", "2009-01-15", "2009-01-16", "2009-01-19", "2009-01-20", "2009-01-21", "2009-01-22", "2009-01-23", "2009-01-27", "2009-01-28", "2009-01-29", "2009-01-30", 
    "2009-02-02", "2009-02-03", "2009-02-04", "2009-02-05", "2009-02-06", "2009-02-09", "2009-02-10", "2009-02-11", "2009-02-12", "2009-02-13", "2009-02-16", "2009-02-17", "2009-02-18", "2009-02-19", "2009-02-20", "2009-02-23", "2009-02-24", "2009-02-25", "2009-02-26", "2009-02-27", 
    "2009-03-02", "2009-03-03", "2009-03-04", "2009-03-05", "2009-03-06", "2009-03-10", "2009-03-11", "2009-03-12", "2009-03-13", "2009-03-16", "2009-03-17", "2009-03-18", "2009-03-19", "2009-03-20", "2009-03-23", "2009-03-24", "2009-03-25", "2009-03-26", "2009-03-27", "2009-03-30", "2009-03-31",
    "2009-04-01", "2009-04-02", "2009-04-03", "2009-04-06", "2009-04-07", "2009-04-08", "2009-04-09", "2009-04-14", "2009-04-15", "2009-04-16", "2009-04-17", "2009-04-20", "2009-04-21", "2009-04-22", "2009-04-23", "2009-04-24", "2009-04-27", "2009-04-28", "2009-04-29", "2009-04-30",
    "2009-05-01", "2009-05-04", "2009-05-05", "2009-05-06", "2009-05-07", "2009-05-08", "2009-05-11", "2009-05-12", "2009-05-13", "2009-05-14", "2009-05-15", "2009-05-18", "2009-05-19", "2009-05-20", "2009-05-21", "2009-05-22", "2009-05-25", "2009-05-26", "2009-05-27", "2009-05-28", "2009-05-29",
    "2009-06-01", "2009-06-02", "2009-06-03", "2009-06-04", "2009-06-05", "2009-06-09", "2009-06-10", "2009-06-11", "2009-06-12", "2009-06-15", "2009-06-16", "2009-06-17", "2009-06-18", "2009-06-19", "2009-06-22", "2009-06-23", "2009-06-24", "2009-06-25", "2009-06-26", "2009-06-29", "2009-06-30",
    "2009-07-01", "2009-07-02", "2009-07-03", "2009-07-06", "2009-07-07", "2009-07-08", "2009-07-09", "2009-07-10", "2009-07-13", "2009-07-14", "2009-07-15", "2009-07-16", "2009-07-17", "2009-07-20", "2009-07-21", "2009-07-22", "2009-07-23", "2009-07-24", "2009-07-27", "2009-07-28", "2009-07-29", "2009-07-30", "2009-07-31",
    "2009-08-03", "2009-08-04", "2009-08-05", "2009-08-06", "2009-08-07", "2009-08-10", "2009-08-11", "2009-08-12", "2009-08-13", "2009-08-14", "2009-08-17", "2009-08-18", "2009-08-19", "2009-08-20", "2009-08-21", "2009-08-24", "2009-08-25", "2009-08-26", "2009-08-27", "2009-08-28", "2009-08-31",
    "2009-09-01", "2009-09-02", "2009-09-03", "2009-09-04", "2009-09-07", "2009-09-08", "2009-09-09", "2009-09-10", "2009-09-11", "2009-09-14", "2009-09-15", "2009-09-16", "2009-09-17", "2009-09-18", "2009-09-21", "2009-09-22", "2009-09-23", "2009-09-24", "2009-09-25", "2009-09-28", "2009-09-29", "2009-09-30",
    "2009-10-01", "2009-10-02", "2009-10-05", "2009-10-06", "2009-10-07", "2009-10-08", "2009-10-09", "2009-10-12", "2009-10-13", "2009-10-14", "2009-10-15", "2009-10-16", "2009-10-19", "2009-10-20", "2009-10-21", "2009-10-22", "2009-10-23", "2009-10-26", "2009-10-27", "2009-10-28", "2009-10-29", "2009-10-30",
    "2009-11-02", "2009-11-04", "2009-11-05", "2009-11-06", "2009-11-09", "2009-11-10", "2009-11-11", "2009-11-12", "2009-11-13", "2009-11-16", "2009-11-17", "2009-11-18", "2009-11-19", "2009-11-20", "2009-11-23", "2009-11-24", "2009-11-25", "2009-11-26", "2009-11-27", "2009-11-30", 
    "2009-12-01", "2009-12-02", "2009-12-03", "2009-12-04", "2009-12-07", "2009-12-08", "2009-12-09", "2009-12-10", "2009-12-11", "2009-12-14", "2009-12-15", "2009-12-16", "2009-12-17", "2009-12-18", "2009-12-21", "2009-12-22", "2009-12-23", "2009-12-24", "2009-12-29", "2009-12-30", "2009-12-31"};
  int i;
  int rc;
  int year, month, day;
  int start_date_position = 0; // the position of the start date in the working_days array.
  int end_date_position = 0;
  const int TOTAL_DAYS = sizeof(working_days)/11; // The total number of days in the working_days array.
 
  // Check that input is valid
  rc = sscanf(start_date, "%4d-%2d-%2d", &year, &month, &day);
  if ((rc != 3) ||
	  (year < 2009) || (year > 2009) ||
	  (month < 1) || (month > 12) ||
	  (day < 1) || (day > 31)) {
    lr_error_message("Start date (%s) is invalid or out of range.", start_date);
    return -1;
  }
  rc = sscanf(end_date, "%4d-%2d-%2d", &year, &month, &day);
  if ((rc != 3) ||
	  (year < 2009) || (year > 2009) ||
	  (month < 1) || (month > 12) ||
	  (day < 1) || (day > 31)) {
	lr_error_message("End date (%s) is invalid or out of range.", end_date);
	return -1;
  }
  if (strcmp(start_date, end_date) > 0) {
    lr_error_message("Start date (%s) must occur after end date (%s)", start_date, end_date);
    return -1;
  }
 
  // Find the position of the start date (or next working day)
  for (i=0; i<TOTAL_DAYS; i++) {
	if (strcmp(working_days[i], start_date) >= 0) {
	  start_date_position = i;
	  break;
	}
  }
 
  // Find the position of the end date (or next working day)
  for (i=start_date_position; i<TOTAL_DAYS; i++) {
	if (strcmp(working_days[i], end_date) >= 0) {
	  end_date_position = i;
	  break;
	}
  }  
 
  return (end_date_position - start_date_position);
}

This is a very good function but I wanted to alter this to give me only the working days excluding the weekendsand then maybe extending it a bit further to negate the bank holidays. Any ideas?

Stuart Moncrieff
Stuart Moncrieff

You probably want to use the Date/Time parameter type instead of a function like this. The Date/Time parameter allows you to specify “working days only”.

If you wanted to also exclude holidays, you could make an array of holiday dates, and write some code that would compare the value in your Date/Time parameter with the dates in your exclusion list. If there was a match, you could call lr_advance_param() to get the next date value.

Leave a Reply