Using Perl CGI to figure out how much time has elapsed since an
event and create an action
by
TDavid
Let's say we need to figure out how long it has been since something happened and we either don't know how (to use) or what the built-in functions are to do this. Or perhaps you'd like to just understand how to do it without relying on those built-in functions. It seems like something like this should be really easy to do (in some languages it is), but it is difficult finding tutorials which will explain how to accomplish this, so I have decided to show you one technique that you can use in your programs to accomplish the comparison of time. You only need to know how to get a timestamp and the rest we can build raw (that is my way of saying without using external libraries or built-in functions or modules). I am going to use Perl CGI as an example, but this same technique can be used in all languages (although of course the base function names will likely change).
All computers have an internal clock that is always ticking and the existing format of timestamps will become invalid at midnight January 1, 2038. Thus this tutorial will probably be obsoleted about 37 years since it's creation. Let's take this number:
965587619
This is a timestamp of the number of seconds since January 1, 1970, which is sometimes also referred to as the computer epoch. How do we know this besides me telling you? Use the handy table below as a guide:
second |
minute |
hour |
day |
week |
month (30 days) |
|
| seconds | 1 |
60 |
3600 |
86400 |
604800 |
2592000 |
One year is equal to 86400 times 365 days = 31,536,000 seconds. So if you do your math you can figure that today (depending on when you read this article) it has been about 31 years x 31,536,000 + 8 months (8 times 2,592,000) + the days since I wrote this article. The number 965587619 should no longer look so cryptic to you now as if you have a calculator that will go that many digits, you'll see that today is approximately this number of seconds since January 1, 1970. Why do I say approximately? Read on...
Now, the next time you see a timestamp you can say, "ahh, that is the number of seconds since January 1, 1970, the computer epoch!" (and people will probably look at you like you are on something). Seriously, as a programmer, though, you can tap into this clock and effectively use a stopwatch to determine the passage of time. That is what we are really going to focus on: stopping the time to mark an event. Consider the code below:
#!/usr/bin/perl
$past = 957138777;
$present = time;
$seconds = int($present - $past);
print "Content-type: text/html\n";
print "Seconds elapsed: $seconds";
Every time you run the script above, the seconds elapsed will increase. Why? Because, as I said above, time is always incrementing. Now how do we stop the time? You need to grab a timestamp from the system. In Perl (and in C) we can use the built-in function time to do this very easily. We log the time that something happens first, whether it be a post on a messageboard, a hit to a webpage, whatever event you want to capture and then we assign that value to $past above. By comparing the two we can now determine the number of seconds that have elapsed since that event happened. Simple mathematics will help us calculate how many minutes, hours days, weeks, etc. See the table above for the math I've already calculated how many seconds in a minute, hour, day, week and month (30 days). Now that we understand these things we can divide the results into $hours, $days, $weeks, $months
$hours = int($seconds / 3600);
$days = int($hours / 24);
$weeks = int($days / 7);
$months = int($days / 30);
The Perl int function will force the result to always be an integer (whole number like 1, 500, 5000, etc) instead of some floating point number like 1.2335 or 500.35395. Now let's make a subroutine to call and pass it the timestamp a prior event happened as an argument.
sub passing_time {
$past = $_[0];
$present = time;
$seconds = int($present - $past);
return($seconds);
}
$elapsed = &passing_time(957138777);
We need a file to actually write the event and timestamps too. Let's create a subroutine to write and log the times to the timesfile.txt file with an "event|$timestamp\n" format.
sub log_event {
open(FILEHANDLE, "</path/to/timesfile.txt");
$now = time;
print FILEHANDLE "$_[0]|$now\n";
close(FILEHANDLE);
}
&log_event("$ENV{'HTTP_REFERER'}");
We're getting closer to something useful now! Let's cycle through a file with a bunch of timestamps in the format "event|$timestamp\n" and snag the value to dynamically place the amount of seconds elapsed in an array called @elapsed.
open(FILEHANDLE, "/path/to/timesfile.txt");
@contents = <FILEHANDLE>;
close(FILEHANDLE);
foreach $each(@contents) {
chomp($each);
($event, $happened) = split(/\|/, $each);
$elapsed = &passing_time($happened);
$minutes = int($elapsed / 60);
$hours = int($elapsed / 3600);
print "<b>$event</b> stopped by $elapsed seconds, $minutes
minutes and $hours hours ago<br>";
}
We now have a script that will log the page referrer and the time they stopped by and will report back to us how much time since that referrer stopped by our page, but now we want to compare those events in time and take an action. You may notice numbers are being rounded here (via the int function), we could build in logic that would deal with floating point numbers, but I'll save that tech article for another day.
Comparing events to a time period
Let's say we only want to keep referrers in this file within the last 5 minutes and purge all referrers that are older than 5 minutes. Since we are working in seconds here, it is helpful to consult the table above and see that 5 minutes equals 300 seconds (5 x 60 = 300).
$comparison = 300;
if($elapsed > $comparison) { $each = ""; }
Now let's add that into our main section of code we're working on:
foreach $each(@contents) {
chomp($each);
($event, $happened) = split(/\|/, $each);
$elapsed = &passing_time($happened);
$minutes = int($elapsed / 60);
$hours = int($elapsed / 3600);
$comparison = 300;
if($elapsed > $comparison) {
$each = "";
$rebuild = 1;
} else {
print "<b>$event</b>
stopped by $elapsed seconds, $minutes minutes and $hours hours ago<br>";
}
}
Now notice we set a flag called $rebuild to be true ($rebuild =1) if we come up to a number of seconds that is greater than 300 and at the same time we set the index in the array to equal nothing. The last step is actually rebuilding the log if there has been any changes to it and only writing true entries back into the file:
if($rebuild) { &rebuild_log; }
sub rebuild_log {
open(FILEHANDLE, ">/path/to/timesfile.txt");
foreach $each(@contents) {
chomp($each);
($event, $happened) = split(/\|/, $each);
if($each) { print FILEHANDLE "$event|$happened\n"; }
}
close(FILEHANDLE);
}
So let's look at the entire code which now will keep track of all referring urls and the time that they come to the site, and it will automatically purge old referring urls every 5 minutes. There is a slight problem with the code above, it doesn't have any file locking, and if you have a lot of traffic to the page where you are testing this out, you might end up with some file corruption if you don't lock the files. Also we don't want it to show a visitor the minute they are referred, so let's add a flag which makes sure the elapsed time is greater than zero. You'll see if you go back with your browser that your entry shows up the second time. So let's add some filelocking to our final example so that the file does not become corrupted while tracking these referring urls and timestamps:
#!/usr/bin/perl
print "Content-type: text/html\n\n";
if($ENV{'HTTP_REFERER'} ne "") {
&log_event("$ENV{'HTTP_REFERER'}");
}
open(FILEHANDLE, "</path/to/timesfile.txt");
flock(FILEHANDLE, 2);
@contents = <FILEHANDLE>;
flock(FILEHANDLE, 8);
close(FILEHANDLE);
foreach $each(@contents) {
chomp($each);
($event, $happened) = split(/\|/, $each);
$elapsed = &passing_time($happened);
$minutes = int($elapsed / 60);
$hours = int($elapsed / 3600);
$comparison = 300;
if($elapsed > $comparison) {
$each = "";
$rebuild = 1;
} else {
if($elapsed > 0) {
print "<b>$event</b>
stopped by $elapsed seconds, $minutes minutes and $hours hours ago<br>";
}
}
}
if($rebuild) { &rebuild_log; }
sub passing_time {
$past = $_[0];
$present = time;
$seconds = int($present - $past);
return($seconds);
}
sub log_event {
open(FILEHANDLE, ">>/path/to/timesfile.txt");
flock(FILEHANDLE, 2);
$now = time;
print FILEHANDLE "$_[0]|$now\n";
flock(FILEHANDLE, 8);
close(FILEHANDLE);
}
sub rebuild_log {
open(FILEHANDLE, ">/path/to/timesfile.txt");
flock(FILEHANDLE, 2);
foreach $each(@contents) {
chomp($each);
($event, $happened) = split(/\|/, $each);
if($each) { print FILEHANDLE "$event|$happened\n"; }
}
flock(FILEHANDLE, 8);
close(FILEHANDLE);
}
Now what if we decided, we want to track 24 hours at a time? All you'd have to do is change $comparison to 86400 (24 hours = 86400 seconds). What if wanted to track for 30 days and get rid of all referrers older than 30 days? We'd change comparison to 2592000 (30 days = 2592000 seconds). Now you should better understand how to deal with taking an action with the passage of time. I have changed the script to track by the last 2 hours in the example below.
Click here for
working example of this script
Click here to view source code
What happens after 2038?
If you asked this question of yourself already, then give yourself a bronze star becaue this is a very good question. My guess is that an extra digit will be added to the timestamp which will add, if my math is correct (I was never good at math, though) an extra 250 years or so (if 9 digits = approximately 68 years then from 1970 - 2038 = 68 years, thus how many years will 10 digits yield?). If the extra digit (or digits) is not added, a lot of code in 2038 will need to be altered around the globe. This might be the real Y2K, as programs which have relied on the timestamp (the best thing TO rely on at this point) shouldn't have missed a beat when 2000 came along, however almost ALL programs inherently use the system clock at some lower level language and when you stop and think about that a minute, what if the system time becomes invalid at midnight January 1, 2038? This is a very fascinating topic for another day and a very real problem that will one day face the programming/tech community if a solution (unbeknowst to me at the time of writing this article, of course) has not already been explored and decided upon.
TDavid is co-owner, programmer and webmaster for several sites devoted to scripting including his own http://www.tdscripts.com/ His current projects include a new weekly tech radio show "2 Hours Of Tech with TDavid & Company" which airs live every Friday from 2-4pm PST. The show as well as prior archives can be accessed by visiting http://www.scriptschool.com/radio/ During the week you can often find TDavid in the script school chatroom at http://www.scriptschool.com/commons/