Lady_Aleena has asked for the wisdom of the Perl Monks concerning the following question:

Hello again all...

I was trying to add some times up, and well doing it manually was a bit of a pain. I started writing a script and that is when things went badly for me. I can get the raw numbers (with decimals which I don't want). However, I can't get it to do hours, minutes, and seconds in that order.

use strict; use warnings; use List::Util qw(sum); my @times = qw(5:21 8:01 5:37 7:19 5:46 7:44 6:43 7:17 8:02 6:50 7:54 +8:44); # @times are minutes and seconds. my @times_in_seconds; for my $time (@times) { my @individual_time = split(':',$time); my $seconds = ($individual_time[0] * 60) + $individual_time[1]; push @times_in_seconds,$seconds; } my $total_time_in_seconds = sum(@times_in_seconds); my $total_time_in_minutes = $total_time_in_seconds/60; my $total_time_in_hours = $total_time_in_minutes/60;

I was talking to someone on IRC who gave me the following, however, I can't for the life of me figure out how it works even after looking at the docs for the module.

use Date::Manip; my $t = Date::Manip::Delta->new; for (@times) { $t = $t->calc($t->new_delta($_)); } print $t->printf("%hyh:%02mmm:%02sss\n");

I am very confused. I have never written code that looks like that. It is the -> and the printf (which has never made sense to me) that have me stumped. I haven't written a from scratch script for several months so forgive me if I missed the obvious.

Have a nice day!
Lady Aleena

Replies are listed 'Best First'.
Re: Getting times (weeks, days, hours, minutes, seconds)
by ikegami (Patriarch) on Jul 04, 2010 at 21:09 UTC
    int to remove fractions. Then remove the minutes from the seconds, and remove the hours from the minutes.
    use strict; use warnings; my @times = qw( 5:21 8:01 5:37 7:19 5:46 7:44 6:43 7:17 8:02 6:50 7:54 + 8:44 ); my $total_seconds; for my $time (@times) { my ($mins, $secs) = split(/:/, $time); $total_seconds += $mins*60 + $secs; } my $total_minutes = int($total_seconds/60); $total_seconds -= $total_minutes*60; my $total_hours = int($total_minutes/60); $total_minutes -= $total_hours*60; printf("%s hours, %s minutes, %s seconds\n", $total_hours, $total_minutes, $total_seconds, );

    It is the -> and the printf (which has never made sense to me) that have me stumped.

    $t->printf is a method call just like the preceding $t->calc, $t->new_delta and Date::Manip::Delta->new.

      Thanks to everyone who responded. I don't want to spam the place responding to everyone, so I hope you all don't mind just this one post. Also, thanks for showing me modules and functions that are new to me. I will look into them further if I need them later. For something as simple as this, I decided to just do a little math myself.

      I hope that you all aren't disappointed that I just used return instead of printf in my subroutine. The whole print vs. printf conversation seems best left for a completely independent thread.

      Leap seconds? Where have I been? :)

      Looks like the -> may have something to do with objects and classes, which I am not versed in yet. I have read over several perldocs on objects and barely get it. I haven't dealt with objects in any code I have written. Objects seems to be another topic left to an independent thread. I think I would need to see a full example all together to begin to grasp the subject.

      Again, thank you all for helping me out. Have some cookies!

      use strict; use warnings; #I made my variables more semantic. @times was just to general, %durat +ions is a better word, thanks to those who used it. :) my %durations = ( 's1' => [qw(3:58 2:48 4:28 5:06 6:50 5:33 4:05 3:29 4:48 6:19)], 's2' => [qw(5:33 5:08 5:30 6:52 6:56 6:48 6:34 5:43 6:50 8:32 6:22 8 +:39)], 's3' => [qw(5:21 8:01 5:37 7:19 5:46 7:44 6:43 7:17 8:02 6:50 7:54 8 +:44)], ); # %durations are minutes and seconds. sub get_duration { my ($var) = @_; my $total_seconds; for my $duration (@{$durations{$var}}) { my ($minutes,$seconds) = split(':',$duration); # The change here m +ade the line much easier to read. :) $total_seconds += ($minutes * 60) + $seconds; # This eliminated my + need to create another array then adding the values of that array. : +) } my $total_minutes = int($total_seconds/60); #I totally forgot about +int. :S my $total_hours = int($total_minutes/60); my $print_seconds = $total_seconds % 60; my $print_minutes = $total_minutes % 60; return $total_hours.':'.$print_minutes.':'.$print_seconds."\n"; } print get_duration('s1'); print get_duration('s2'); print get_duration('s3');
      Have a nice day!
      Lady Aleena

        The whole print vs. printf conversation seems best left for a completely independent thread.

        I didn't realise there was any controversy. It's not even a question of print vs printf but one of

        my ($h,$m,$s) = ...; $m = "0$m" if $m < 10; $s = "0$s" if $s < 10; my $dur = "$h:$m:$s";

        vs

        my ($h,$m,$s) = ...; my $dur = sprintf("%d:%02d:%02d", $h,$m,$s);

        Right now, you are creating timestamps like 1:3:43 instead of 1:03:43. printf and sprintf are quite apt at formatting this kind of data, but you don't have to use them. Take your pick.

        %d Signed 32/64-bit integer %2d ...space padded (on the left) to occupy (at least) 2 columns %02d ...zero padded (on the left) to occupy (at least) 2 columns

        hope that you all aren't disappointed that I just used return instead of printf in my subroutine.

        No, separation of calculation and I/O is a good thing. However,

        print get_duration('s1');

        should be

        print get_duration($durations{s1});

        or

        print get_duration(@{ $durations{s1} });

        (with the corresponding changes within get_duration). There's no reason for %durations to be global.

        Leap seconds? Where have I been? :)

        That post tries to show why DateTime wasn't a good solution for this problem, not how to solve the problem using DateTime. Sounds like it worked.

        I have read over several perldocs on objects and barely get it

        In short,

        • Objects represent some object (e.g. a specific user, a specific window, a specific timestamp, etc).
        • A type of object (e.g. user, window, timestamp) is called a class.
        • Objects contain data about that object ("attributes") and the class provides functions to manipulate the objects ("methods").
        • Methods can provide access to the attributes of an object (e.g. my $title = $window->get_title() and $window->set_title($title);)
        • Methods can provide cause actions to be performed. (e.g. $window->close();)
Re: Getting times (weeks, days, hours, minutes, seconds)
by Proclus (Beadle) on Jul 05, 2010 at 06:22 UTC
    You should look at DateTime. You can do all sorts of date math with a very easy interface. The examples will guide you through it.

      The was nothing requiring date math in the OP, but DateTime also does time math. Since the OP is dealing with durations, she'd need DateTime::Duration.

      Unfortunately, it doesn't produce the result one would expect because DateTime(::Duration) realises that not every minute has 60 seconds. Some are longer due to leap seconds.

      use strict; use warnings; use DateTime::Duration qw( ); my @times = qw( 5:21 8:01 5:37 7:19 5:46 7:44 6:43 7:17 8:02 6:50 7:54 + 8:44 ); my $dur = DateTime::Duration->new(); for my $time (@times) { my ($mins, $secs) = split(/:/, $time); $dur += DateTime::Duration->new( minutes => $mins, seconds => $secs, ); } printf("%s hours, %s minutes, %s seconds\n", $dur->in_units(qw( hours minutes seconds )) );
      1 hours, 19 minutes, 378 seconds

      From the docs, the only conversions possible are:

      • years ⇔ months
      • weeks ⇔ days
      • hours ⇔ minutes
      • seconds ⇔ nanoseconds

      Now, you can use a reference datetime to determine minute length.

      ... my $dur2 = do { my $ref = DateTime->new( year => 2006, month => 1, day => 1, hour => 0, minute => 0, second => 0, time_zone => 'UTC', ); $ref + $dur - $ref }; printf("%s hours, %s minutes, %s seconds\n", $dur2->in_units(qw( hours minutes seconds )) ); my $dur3 = do { my $ref = DateTime->new( year => 2005, month => 12, day => 31, hour => 23, minute => 59, second => 59, time_zone => 'UTC', ); $ref + $dur - $ref }; printf("%s hours, %s minutes, %s seconds\n", $dur3->in_units(qw( hours minutes seconds )) );
      1 hours, 19 minutes, 378 seconds 1 hours, 25 minutes, 18 seconds # All of the minutes had 60 seconds. 1 hours, 25 minutes, 19 seconds # One of the minutes had 61 seconds.

      Update: Added example where leap seconds affect the result.

        Unfortunately, it doesn't produce the result one would expect because DateTime(::Duration) realises that not every minute has 60 seconds. Some are longer due to leap seconds.

        From the docs, the only conversions possible are:

        • years ⇔ months
        • weeks ⇔ days
        • hours ⇔ minutes
        • seconds ⇔ nanoseconds
        Ah, so, Date::Time favours geek correctness over usability. And by doing so, it's not POSIX compliant (as POSIX says leap seconds are to be ignored). While taking care of leap seconds is useful in some cases, stubbornly refusing to do second/minute conversion because of a leap second every few years most of society ignores without problems makes the module far less useful than it could be.
Re: Getting times (weeks, days, hours, minutes, seconds)
by jethro (Monsignor) on Jul 05, 2010 at 09:37 UTC

    There is an easy way to understand -> (at least I hope so):

    $something->func($x);

    is nearly the same as

    func($something,$x);

    with func a subroutine out of the package (called class) that $something belongs to

    There is also an easy way to understand printf:

    printf("bla %xyz chink %ru wong",$a,$b);

    is somewhat equivalent to

    print "bla $a chink $b wong";

    The printf often does some conversions of the variables, but basically thats it.

    Now in the code someone on IRC gave you the printf is not the normal builtin perl printf, since it was called with the -> syntax. Instead it must be subroutine out of the package $t belongs to. What package does $t belong to? Date::Manip::Delta, since $t was created with my $t = Date::Manip::Delta->new;

    UPDATE: Added the word 'nearly'

      $something->func($x) and func($something,$x) aren't exactly equivalent. The second one will ignore inheritance.
      $something->func($x)
      is rarely equivalent to
      func($something, $x)
      More like
      Class::Or::Superclass::Of::Something::func($something, $x)

      Your explanation just reinforces the OP's misconception that printf gets called, but it's not involved one bit.

        Not if she reads the complete sentence, and definitely not if she reads the complete post. And if you throw "Class::Or::Superclass::Of::Something::func($something, $x)" at someone unfamiliar with basic concepts of perl OO, you might as well offer a zen koan ;-).
Re: Getting times (weeks, days, hours, minutes, seconds)
by DrHyde (Prior) on Jul 05, 2010 at 10:06 UTC
    I am very confused. I have never written code that looks like that. It is the -> and the printf (which has never made sense to me) that have me stumped.

    $foo->bar() is a method call on an object, and Foo->bar() is a method call on a class. See perlboot and perltoot for tutorials.

    printf is one of Date::Manip::Delta's methods, so you can find its documentation there. Or if it's the whole idea of printf() and its friends and relations that confuses you even after reading the sprintf manpage on your system, then I suggest reading chapters 1 and 7 of Kernighan and Ritchie's "The C Programming Language".

Re: Getting times (weeks, days, hours, minutes, seconds)
by roboticus (Chancellor) on Jul 05, 2010 at 12:21 UTC

    Lady Aleena:

    Actually, you weren't that far off. I avoid the decimal bits by removing the surplus seconds before the division.

    For example, if you have 77 seconds, you first tell how many leftover seconds you'll get in the answer, like so: $secs = 77 % 60 giving you 17. Then you subtract those 17 seconds from your time in seconds to give you an integral multiple of 60: $t=($t-$secs)/60. You can extend it as far as you like:

    #!/usr/bin/perl -w use strict; use warnings; print time_to_string(1234), "\n"; print time_to_string(12345), "\n"; print time_to_string(123456), "\n"; sub time_to_string { my $t = shift; # t=total time in seconds my $seconds = $t % 60; $t=($t-$seconds)/60; # t now has minutes my $minutes = $t % 60; $t=($t-$minutes)/60; # t now has hours my $hours = $t % 24; $t=($t-$hours) /24; # t now has days my $str = ''; $str .= "$t days, " if $t>0; $str .= sprintf "% 2u:% 2u:% 2u", $hours, $minutes, $seconds; }

    Running this gives me:

    roboticus@Boink:~$ ./848005.pl 0:20:34 3:25:45 1 days, 10:17:36 roboticus@Boink:~$

    ...roboticus

Re: Getting times (weeks, days, hours, minutes, seconds)
by Marshall (Canon) on Jul 05, 2010 at 14:04 UTC
    Here is another way... Simply, sum the minutes and seconds and use gmtime() to convert the total number of seconds to hours, minutes, seconds. This of course assumes some time delta from the epoch number of seconds(0) for each item. Which is fine in this case as we are measuring duration, not absolute date values.

    The year and day of week, etc are all meaningless in this context so just throw them away. Leap seconds, leap years don't matter as long as we are talking about data like your example.

    #!/usr/bin/perl -w use strict; my @times = qw( 5:21 8:01 5:37 7:19 5:46 7:44 6:43 7:17 8:02 6:50 7:54 + 8:44 ); my $total_minutes; my $total_seconds; foreach my $time (@times) { my ($m,$s) = split(':',$time); $total_minutes += $m; $total_seconds += $s; } my ($h,$m,$s) = (gmtime($total_minutes*60+$total_seconds))[2,1,0]; print "$h hours, $m minutes, $s seconds"; #1 hours, 25 minutes, 18 seconds
    The list slice isn't necessary, but I like to line up the left hand side vars so that they are in the order that I use them later. my ($s,$m,$h) would be fine without the slice.