Working with localtime in Perl

By: Paul Corr


Overview

In taking one of the online Intro to Perl courses, one of the assignments required working with the built-in 'localtime()' function and the related 'Time::localtime' module. Since my reading generated more than a couple "What the heck?" questions when viewing docs or examples, I'll include notes from my exploration of localtime below. Enjoy!

Function 'localtime'

The localtime function is described in the perlfunc.pod as: "[converting] a time as returned by the time function to a 9-element array with the time analyzed for the local time zone." It returns different values, depending on whether it is called in list or scalar context.

Using it as an argument to the print function, which takes its arguments in list context, returns an array of values. Therefore, it is similar to printing an array or list. You will get a concatenated string of numbers. (You can use the join function to separate the values.)

Assigning the result of the function to a scalar, or forcing scalar context with the scalar operator, will return a date/time string. Similarly, using the substr function (to get a substring) is also scalar context. The line containing 'substr' below gets the 1st 3 chars of localtime's scalar context returned string:

    print localtime();        	     # returns 1319121089952521
    print join (" ",localtime() );   # returns 13 19 12 10 8 99 5 252 1
    print scalar localtime();        # returns Fri Sep 10 12:19:13 1999
    $datetime = localtime();         # assignment is scalar context
    print $datetime;                 # returns Fri Sep 10 12:19:13 1999

    # assuming the day the script is run is Friday...
    my $day = (localtime)[6];        # list context
    print $day"\n";                  # returns '5'
    
    # syntax: 'substr EXPR,OFFSET,LEN' (get 1st 3 chars of date/string)
    $dayname = substr((localtime ()), 0, 3); # scalar context 
    print $dayname." \n";                    # returns 'Fri'
The localtime syntax (according to perlfunc) is 'localtime EXPR'. Using the 'localtime' function without an expression, defaults to using the result of the time function as its argument. The following calls are all equivalent (and return '1319121089952521', the concatenated 9-element time array):

    print localtime,"\n";
    print localtime(),"\n";
    print localtime(time),"\n";

Using localtime like this returns a string similar to what you would get using 'ctime' on a UNIX system. The 'perlfunc' entry didn't explain what the EXPR might be so I consulted "Programming Perl" whose entry is similar to perlfunc, but includes an example. As the localtime function returns a list, you can index the list returned like an array. Here is that "Programming Perl" localtime entry example:

    $today = (Sun,Mon,Tue,Wed,Thur,Fri,Sat)[ (localtime)[6] ];

If you run it, it will tell you the three-letter abbreviation for today. Here's an explanation of this compact perl statement...

Nearly all counting in localtime's information fields begins at zero. For example, Sunday and January are zero. Usually, its values are grabbed with an assignment like the following:

    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time)

Values, in order, left to right, are:

The 'localtime[6]' expression in the statement above returns the index-6 value (number for the day of the week) from localtime's (now-indexed) list and that number is then used as an index to the statement's now-indexed list, returning the correct day of the week. No wonder it's difficult for novices to understand perl with examples like this. Many perl idiomatic solutions compress multiple-line solutions like the following:

    @weekdays = (Sun,Mon,Tue,Wed,Thur,Fri,Sat);
    $day      = (localtime)[6];
    $today    = $weekdays[$day];

to one-liners, like the Programming Perl example above. While the longer example is more wordy and creates three variables instead of one, it might be clearer for a novice.

I hadn't seen indexing of literal lists before, so I consulted "Learning Perl." The chapter on lists and arrays shows examples of slice indexing on literal lists, a similar activity. The perldata manpage has the following sentence in the "List Value Constructors" section: "A list value may also be subscripted like a normal array. You must put the list in parentheses to avoid ambiguity." You'll often see "(localtime)[3..5]" used to grab the day/month/year values, for example.

Function Examples

Here are a few 'localtime' function examples.

Basic Calls (Date/Time Array, or String)

The first uses the scalar operator (see perlfunc) to force scalar context, eliminatating the need for the assignment to a scalar variable.

    print scalar(localtime); # Returns: Fri Sep 10 12:19:13 1999

Alternately, if a scalar assignment is used, it is more easily reused.

    my $now = localtime;
    print "Calling 'localtime' function without an expression:\n";
    print "\tThe time/date is: $now.\n";

which returns:

    Calling 'localtime' function without an expression:
        The time/date is: Fri Sep 10 12:19:13 1999

Using localtime as an argument to the 'print' function (list context), will 'unwrap' the list in the output and each list element will be appended to the previous element, displaying one large number, the concatenated, raw 8-element 'localtime' list:

    print localtime"\n"; #Returns: 1319121089952521

You really need to assign the returned list to an array variable (like the next example) to be able to do useful work.

    my @timearray = localtime();
    print "Here are the tab-delimited elements that 'localtime'\n",
          "returns after assignment to an array:\n";
    print "\t", join("\t",@timearray),"\n";

which returns:

    Here are the tab-delimited elements that 'localtime'
    returns after assignment to an array:
        13    19    12    10    8    99    5    252    1

Note: I could have used the simpler 'print "@timearray";' statement to get a space-delimited (instead of tab-delimited) string. If I used 'print @timearray;' using no double-quotes, I would get the (updated) concatenated string (1319121089952521) again. Double quotes around an array variable in the print function will print the separate elements with spaces in the output. (Set with the special $" variable, which has a space character as its default value.)

Get The Current Time

If I just want the time, I extract the hours, minutes and seconds 'fields' from localtime's returned list. Note that seconds are first and I have to reverse the order using the indices, inserting colons, where needed.

    print "Indexing the returned list to get
           just the time in hr:min:sec order:\n";
    my @curr_time = localtime;
    print "\tTime is: $curr_time[2]:$curr_time[1]:$curr_time[0]\n";

which returns:

    Indexing the returned list to get just the time in hr:min:sec order:
        Time is: 11:51:59

or, more simply grab the first three values from the localtime array:

    ($sec,$min,$hr) = localtime();
    print "Time is: $hr:$min:$sec\n"; # Returns: Time is: 11:51:59

Get The Current Date

Here's a quick example to get an American-style (month-day-year) numerical date:
    @f=(localtime)[3..5]; # grabs day/month/year values
    printf "%d-%d-%d\n", $f[1] +1, $f[0], $f[2] + 1900;

    #Returns: 9-12-1999 # on Sept. 12, 1999
We are grabbing '$mday,$mon,$year', which are located at the indicated indices and using printf to set the output format for the numbers (%d), separated by dashes, specified in the parameters that follow the print string. Remember, months are counted from zero and the year is since 1900, hence the addition expressions.

Similarly, we can get a generic date string using the following code:

    ($wkday,$month,$day,$time,$year) = split(/\s+/, localtime);
    print "$day-$month-$year\n";

    #Returns: 12-Sep-1999 # on that date, of course
The split function gives localtime a scalar context and it will return the date/time string, rather than the 9-element array of numbers. We can then grab that string's five elements, separated by spaces using 'split'.

More Uses for localtime

Obviously, this page mostly explores parsing the information localtime returns to get the current date and/or time in a desired format. (See the function examples above.) If you have a perl program that seems to be taking too long you can add print statements using localtime before and after a code block or subroutine to time execution:
    print scalar localtime . "\n";
    retrieve_addresses();
    print scalar localtime . "\n";
While I don't completely comprehend the details, you can give localtime a date/time string from something like a file modification date, and it will return a human-readable date, as it does with 'scalar localtime'. Here's an example I found:

    $file = 'test.txt';
    ($atime,$mtime) = (stat ($file) )[8,9];
    
    print $atime; # returns 3019987479

    print "Last access:\t" . localtime($atime) . "\n";
    print "Last change:\t" . localtime($mtime) . "\n";

    #Returns: Last access:      Sun Sep 12 13:24:39 1999
    #Returns: Last change:      Sun Sep 12 13:24:39 1999

[This last example based on code in 'Perl Annotated Archives" by Martin Brown.] The 'stat' function returns an array of information on a file. We want only the information at index 8 and index 9. These values are in 'seconds since the epoch'. On UNIX, it would be midnight Jan. 1, 1970. On Mac, it is midnight, Jan. 1, 1904. The entry in "Perl in a Nutshell" for time states:

Returns the number o non-leap seconds since January 1, 1970, UTC. The returned value is suitable for feeding to gmtime and localtime, and for comparison with file modification and access times returned by stat, and for feeding to utime.

Note: (UTC is Universal Coordinated Time, formerly Greenwich Mean Time, GMT.) Also, on the Mac, I believe some of the values are duplicated to complete the standard 'stat' information. i.e., the access and modification values may be the same.

Since we know the value of the file modification is returned from stat in epoch seconds, and the time function returns the current time in epoch seconds, you can calculate the time elapsed since the file was last accessed:

    printf "File accessed %.02d minutes and %.02d seconds ago.\n",
            (time - $mtime) / 60, (time - $mtime) % 60;

    #Returns: File accessed 52 minutes and 33 seconds ago.

You get the idea...

Better Output Format

If you want a full text date string, use a hash to map the 'localtime' index values to the appropriate day or month string. The example below uses the more-intuitive 'comma arrow' hash assignment, equivalent to: %weekdays = ("0","Sunday", ...) or %weekdays = qw(0 Sunday 1 Monday...):

    # assign the returned list elements to scalars for ease of use
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
    
    my %weekdays = (
        "0" => "Sunday",
        "1" => "Monday",
        "2" => "Tuesday",
        "3" => "Wednesday",
        "4" => "Thursday",
        "5" => "Friday",
        "6" => "Saturday",
    );
    
    my %months = (
        "0"  => "January",
        "1"  => "February",
        "2"  => "March",
        "3"  => "April",
        "4"  => "May",
        "5"  => "June",
        "6"  => "July",
        "7"  => "August",
        "8"  => "September",
        "9"  => "October",
        "10" => "November",
        "11" => "December",
    );
    
    print "Date is: $weekdays{$wday}, $months{$mon} $mday, ", 
	   1900 + $year, "\n";
    print "---\n";

which returns:

    Date is: Friday, September 10, 1999

Notes on Code Readability

Perl ignores whitespace in program statements so you can make your programs more human-readable by adding spaces in the appropriate places. (Not in strings, of course, and regexes, like s/find/replace/, require a switch [x] to have Perl ignore whitespace: s/find/replace/x See perlop for more detail.) I find added whitespace really helpful when there are a lot of nested parentheses. It's easier to tell what is going on with a more 'open' style. Here is a simple example:

Tight style:
if(localtime->isdst()){

More open style:
if ( localtime->isdst() ) {

Here is the 'weekdays' hash assignment (shown above), written in a 'tight' style, which I find more difficult to read, especially when skimming code quickly.

    my %weekdays = ("0"=>"Sunday","1"=>"Monday","2"=>"Tuesday","3"=>"Wednesday",
		    "4"=>"Thursday","5"=>"Friday","6"=>"Saturday");

Often, especially when learning, I'll work on something and then set it aside for several days, maybe longer. When the code is easily read, it is simpler to get back up to speed on the project

Module Time::localtime

There is also a module version of localtime, the Time::localtime module. It could be a good introduction to using modules and their methods. The elements available, like 'year' in the example given in the localtime.pm embedded documentation, are: (sec, min, hour, mday, mon, year, wday, yday, isdst) The advantage is you don't have to map the numbers from localtime's returned list to named variables, as here:

    #my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);

It's done by the module. So, you can use:

    use Time::localtime;
    printf "Hour is: %d\n", localtime->hour();

which returns:

    Hour is: 13

or testing another returned element:

    if (localtime->isdst()) {
        print "It's Daylight Savings Time.\n";
    }else{
        print "It is Standard Time.\n";
    }

which returns (on Sept. 12th):

    It's Daylight Savings Time.

To get the current time, you can use 'print ctime();' instead of assigning a 'localtime' function call to a scalar and printing the scalar, as above.

    use Time::localtime;
    print ctime();

Also, as near as I can tell, you can't unload the 'Time::localtime' module. So, calls to 'localtime' in the function's syntax will go to the module. For example, the first, commented-out statement using a 'localtime' function call won't work - it returns a reference (the memory address of the module's internally assigned 'tm' object): Time::tm=ARRAY(0x6fc1e90) but the 'Time::localtime' method call in the second example is successful.

    #my $current_date = localtime; # fails with Time::localtime active

    my $current_date = ctime();
    print $current_date;

which returns:

    Fri Sep 10 12:19:13 1999

For comparison, here are the previous statements (after creating the 'weekdays' and 'months' arrays) calling the 'localtime' function:

    print "Date is: $weekdays{$wday}, $months{$mon} $mday, ", 
	   1900 + $year, "\n";
    print "---\n";

To get a full text date, use the hashes defined above, with the 'Time::localtime' module:

    print "Date is: ",
        $weekdays{(localtime->wday)} . ", " .
        $months{(localtime->mon)} . " " .
        $localtime->mday . " " .
        1900 + localtime->year, "\n";
    print "---\n";

[I'm using the 'dot' string concatenation operator to assemble the string's elements with commas and spaces for output.]

Summary

This is a broad introduction to using localtime and determining dates and times. I recommend Chapter 3, "Dates and Times," in the Perl Cookbook from O'Reilly as an extensive exploration of date parsing issues and techniques, including examples using localtime and some of the various 'Date' and 'Time' modules. Several of the other books I consulted are also O'Reilly books. Check the ORA Perl Resource page.

I like to really explore a particular task, like using the localtime function and module to find the date, to test a number of techniques in a limited solution. I see localtime pop up in a number of scripts, called different ways, using its values in interesting assignments. I use Web pages like this one as reference notes. (I updated this page after several months.) I hope you find this descriptive page helpful.

Return to top of page


Paul Corr, 1999