5

Is there a CPAN module that can convert a number of seconds to a human-readable English description of the interval?

secToEng( 125 ); # => 2 min 5 sec
secToEng( 129_600 ); # => 1 day 12 h

The format isn't important as long as it's human-readable.

I know that it would be trivial to implement.

Costique
  • 23,712
  • 4
  • 76
  • 79
Tim
  • 13,904
  • 10
  • 69
  • 101
  • 1
    A lot of people think working with dates and times is trivial, and this is simply not the case. You've done the right thing by looking for an existing module :) – ocharles Sep 25 '11 at 15:24
  • @ocharles: You're definitely right about date handling being hard. I think this case is quite easy though :) – Tim Sep 26 '11 at 05:00

5 Answers5

14

It turns out that Time::Duration does exactly this.

$ perl -MTime::Duration -E 'say duration(125)'
2 minutes and 5 seconds
$ perl -MTime::Duration -E 'say duration(129_700)'
1 day and 12 hours

From the synopsis:

Time::Duration - rounded or exact English expression of durations

Example use in a program that ends by noting its runtime:

my $start_time = time();
use Time::Duration;
# then things that take all that time, and then ends:
print "Runtime ", duration(time() - $start_time), ".\n";

Example use in a program that reports age of a file:

use Time::Duration;
my $file = 'that_file';
my $age = $^T - (stat($file))[9];  # 9 = modtime
print "$file was modified ", ago($age);
Tim
  • 13,904
  • 10
  • 69
  • 101
10

DateTime can be used:

#!/usr/bin/env perl

use strict;
use warnings;

use DateTime;
use Lingua::EN::Inflect qw( PL_N );

my $dt = DateTime->from_epoch( 'epoch' => 0 );
$dt = $dt->add( 'seconds' => 129_600 );
$dt = $dt - DateTime->from_epoch( 'epoch' => 0 );

my @date;
push @date, $dt->days . PL_N( ' day', $dt->days ) if $dt->days;
push @date, $dt->hours . PL_N( ' hour', $dt->hours ) if $dt->hours;
push @date, $dt->minutes . PL_N( ' minute', $dt->minutes ) if $dt->minutes;
push @date, $dt->seconds . PL_N( ' second', $dt->seconds ) if $dt->seconds;

print join ' ', @date;

Output

1 day 12 hours
Alan Haggai Alavi
  • 72,802
  • 19
  • 102
  • 127
  • 6
    I have to admit that the “xxx(s)” notation is a personal pet peeve of mine. If they can do anything, computers *should* be able to count and distinguish `1 day` vs `2 days`. :( Note that prefix notation as in `Days: 1` doesn’t have this issue. – tchrist Sep 25 '11 at 14:43
  • However, just "$x day" . ($x > 1 ? 's' : '') is a sloppy way of handling this. If you always use translation libraries to build your strings, this comes for free. – ocharles Sep 25 '11 at 15:37
  • 1
    You only fixed the 'easiest' part of the problem, converting seconds to something else. – MichielB Sep 25 '11 at 18:19
  • *[MichielB](http://stackoverflow.com/users/114904/michielb)*: The answer addresses **just** the problem in the question. With [`DateTime`](http://search.cpan.org/perldoc?DateTime), other conversions are easy as well. – Alan Haggai Alavi Sep 25 '11 at 18:46
  • 1
    Lingua::EN::Inflect is an interesting solution to the pluralization problem. https://metacpan.org/module/Lingua::EN::Inflect – Schwern Sep 26 '11 at 10:16
  • *[Schwern](http://stackoverflow.com/users/14660/schwern)*: I have just updated my answer to include [`Lingua::EN::Inflect`](https://metacpan.org/module/Lingua::EN::Inflect). Thank you. – Alan Haggai Alavi Sep 26 '11 at 10:50
5

Since Perl v5.9.5, the modules Time::Seconds and Time::Piece are part of the core Perl distribution. So you can use them without installing additional modules.

perl -MTime::Seconds -e 'my $s=125; my $ts=new Time::Seconds $s; print $ts->pretty, "\n"'
# 2 minutes, 5 seconds

perl -MTime::Seconds -e 'my $s=129_600; my $ts=Time::Seconds->new($s); print $ts->pretty, "\n"'
# 1 days, 12 hours, 0 minutes, 0 seconds

You can also use stuff like $ts->weeks, $ts->minutes, etc.

tchrist
  • 78,834
  • 30
  • 123
  • 180
mivk
  • 13,452
  • 5
  • 76
  • 69
2

I wasn't able to find such code a while back, so I wrote these two routines. The second sub uses the first and does what you are looking for.

#-----------------------------------------------------------
# format_seconds($seconds)
# Converts seconds into days, hours, minutes, seconds
# Returns an array in list context, else a string.
#-----------------------------------------------------------

sub format_seconds {
    my $tsecs = shift;

    use integer;
    my $secs  = $tsecs % 60;
    my $tmins = $tsecs / 60;
    my $mins  = $tmins % 60;
    my $thrs  = $tmins / 60;
    my $hrs   = $thrs  % 24;
    my $days  = $thrs  / 24;

    if (wantarray) {
        return ($days, $hrs, $mins, $secs);
    }

    my $age = "";
    $age .= $days . "d " if $days || $age;
    $age .= $hrs  . "h " if $hrs  || $age;
    $age .= $mins . "m " if $mins || $age;
    $age .= $secs . "s " if $secs || $age;

    $age =~ s/ $//;

    return $age;
}

#-----------------------------------------------------------
# format_delta_min ($seconds)
# Converts seconds into days, hours, minutes, seconds
# to the two most significant time units.
#-----------------------------------------------------------

sub format_delta_min {
    my $tsecs = shift;

    my ($days, $hrs, $mins, $secs) = format_seconds $tsecs;

    # show days and hours, or hours and minutes, 
    # or minutes and seconds or just seconds

    my $age = "";
    if ($days) {
        $age = $days . "d " . $hrs . "h";
    } 
    elsif ($hrs) {
        $age = $hrs . "h " . $mins . "m";
    } 
    elsif ($mins) {
        $age = $mins . "m " . $secs . "s";
    }
    elsif ($secs) {
        $age = $secs . "s";
    }

    return $age;
}
Bill Ruppert
  • 8,956
  • 7
  • 27
  • 44
  • 1
    Thanks for supplying some code. I was mostly interested in knowing whether there's already such a CPAN module out there, or if one should be written. – Tim Sep 25 '11 at 13:40
  • 1
    The question was not 'Please write me a sub'. – MichielB Sep 25 '11 at 18:18
  • 3
    I didn't write a sub for him. I shared some code I wrote for myself years ago. Thought that would be a friendly thing to do. – Bill Ruppert Sep 25 '11 at 19:45
2

The DateTime modules are what you want.

DateTime::Duration and DateTime::Format::Duration will do what you need.

cjm
  • 61,471
  • 9
  • 126
  • 175
Steven
  • 406
  • 5
  • 10