| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Time::Duration; | 
| 2 |  |  |  |  |  |  | $Time::Duration::VERSION = '1.21'; | 
| 3 | 2 |  |  | 2 |  | 11581 | use 5.006; | 
|  | 2 |  |  |  |  | 13 |  | 
| 4 | 2 |  |  | 2 |  | 11 | use strict; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 39 |  | 
| 5 | 2 |  |  | 2 |  | 10 | use warnings; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 75 |  | 
| 6 | 2 |  |  | 2 |  | 11 | use constant DEBUG => 0; | 
|  | 2 |  |  |  |  | 10 |  | 
|  | 2 |  |  |  |  | 1737 |  | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | require Exporter; | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | our @ISA         = ('Exporter'); | 
| 11 |  |  |  |  |  |  | our @EXPORT      = qw( later later_exact earlier earlier_exact | 
| 12 |  |  |  |  |  |  | ago ago_exact from_now from_now_exact | 
| 13 |  |  |  |  |  |  | duration duration_exact | 
| 14 |  |  |  |  |  |  | concise | 
| 15 |  |  |  |  |  |  | ); | 
| 16 |  |  |  |  |  |  | our @EXPORT_OK   = ('interval', @EXPORT); | 
| 17 |  |  |  |  |  |  | our $MILLISECOND = 0; | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | # ALL SUBS ARE PURE FUNCTIONS | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | sub concise ($) { | 
| 24 | 31 |  |  | 31 | 1 | 49 | my $string = $_[0]; | 
| 25 | 31 |  |  |  |  | 39 | DEBUG and print "in : $string\n"; | 
| 26 | 31 |  |  |  |  | 76 | $string =~ tr/,//d; | 
| 27 | 31 |  |  |  |  | 145 | $string =~ s/\band\b//; | 
| 28 | 31 |  |  |  |  | 183 | $string =~ s/\b(year|day|hour|minute|second)s?\b/substr($1,0,1)/eg; | 
|  | 46 |  |  |  |  | 186 |  | 
| 29 | 31 |  |  |  |  | 77 | $string =~ s/\b(millisecond)s?\b/ms/g; | 
| 30 | 31 |  |  |  |  | 169 | $string =~ s/\s*(\d+)\s*/$1/g; | 
| 31 | 31 |  |  |  |  | 117 | return $string; | 
| 32 |  |  |  |  |  |  | } | 
| 33 |  |  |  |  |  |  |  | 
| 34 | 74 |  |  | 74 | 1 | 5311 | sub later          { interval(      $_[0], $_[1], ' earlier',  ' later',    'right then'); } | 
| 35 | 33 |  |  | 33 | 1 | 2575 | sub later_exact    { interval_exact($_[0], $_[1], ' earlier',  ' later',    'right then'); } | 
| 36 | 8 |  |  | 8 | 1 | 451 | sub earlier        { interval(      $_[0], $_[1], ' later',    ' earlier',  'right then'); } | 
| 37 | 27 |  |  | 27 | 1 | 2036 | sub earlier_exact  { interval_exact($_[0], $_[1], ' later',    ' earlier',  'right then'); } | 
| 38 | 8 |  |  | 8 | 1 | 437 | sub ago            { interval(      $_[0], $_[1], ' from now', ' ago',      'right now');  } | 
| 39 | 27 |  |  | 27 | 1 | 1963 | sub ago_exact      { interval_exact($_[0], $_[1], ' from now', ' ago',      'right now');  } | 
| 40 | 8 |  |  | 8 | 1 | 426 | sub from_now       { interval(      $_[0], $_[1], ' ago',      ' from now', 'right now');  } | 
| 41 | 27 |  |  | 27 | 1 | 2130 | sub from_now_exact { interval_exact($_[0], $_[1], ' ago',      ' from now', 'right now');  } | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 
| 44 |  |  |  |  |  |  | sub duration_exact { | 
| 45 | 4 |  |  | 4 | 1 | 235 | my $span = $_[0];   # interval in seconds | 
| 46 | 4 |  | 100 |  |  | 24 | my $precision = int($_[1] || 0) || 2;  # precision (default: 2) | 
| 47 | 4 | 100 |  |  |  | 23 | return '0 seconds' unless $span; | 
| 48 | 2 |  |  |  |  | 9 | _render('', | 
| 49 |  |  |  |  |  |  | _separate(abs $span)); | 
| 50 |  |  |  |  |  |  | } | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | sub duration { | 
| 53 | 21 |  |  | 21 | 1 | 1420 | my $span = $_[0];   # interval in seconds | 
| 54 | 21 |  | 100 |  |  | 109 | my $precision = int($_[1] || 0) || 2;  # precision (default: 2) | 
| 55 | 21 | 100 |  |  |  | 49 | return '0 seconds' unless $span; | 
| 56 | 18 |  |  |  |  | 46 | _render('', | 
| 57 |  |  |  |  |  |  | _approximate($precision, | 
| 58 |  |  |  |  |  |  | _separate(abs $span))); | 
| 59 |  |  |  |  |  |  | } | 
| 60 |  |  |  |  |  |  |  | 
| 61 |  |  |  |  |  |  | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  | sub interval_exact { | 
| 64 | 119 |  |  | 119 | 0 | 174 | my $span = $_[0];                    # interval, in seconds | 
| 65 |  |  |  |  |  |  | # precision is ignored | 
| 66 | 119 | 100 |  |  |  | 308 | my $direction = ($span < 0) ? $_[2]  # what a neg number gets | 
|  |  | 100 |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | : ($span > 0) ? $_[3]  # what a pos number gets | 
| 68 |  |  |  |  |  |  | : return        $_[4]; # what zero gets | 
| 69 | 114 |  |  |  |  | 224 | _render($direction, | 
| 70 |  |  |  |  |  |  | _separate($span)); | 
| 71 |  |  |  |  |  |  | } | 
| 72 |  |  |  |  |  |  |  | 
| 73 |  |  |  |  |  |  | sub interval { | 
| 74 | 103 |  |  | 103 | 0 | 157 | my $span = $_[0];                     # interval, in seconds | 
| 75 | 103 |  | 100 |  |  | 422 | my $precision = int($_[1] || 0) || 2; # precision (default: 2) | 
| 76 | 103 | 100 |  |  |  | 264 | my $direction = ($span < 0) ? $_[2]   # what a neg number gets | 
|  |  | 100 |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | : ($span > 0) ? $_[3]   # what a pos number gets | 
| 78 |  |  |  |  |  |  | : return        $_[4];  # what zero gets | 
| 79 | 92 |  |  |  |  | 173 | _render($direction, | 
| 80 |  |  |  |  |  |  | _approximate($precision, | 
| 81 |  |  |  |  |  |  | _separate($span))); | 
| 82 |  |  |  |  |  |  | } | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | #~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~# | 
| 85 |  |  |  |  |  |  | # | 
| 86 |  |  |  |  |  |  | # The actual figuring is below here | 
| 87 |  |  |  |  |  |  |  | 
| 88 | 2 |  |  | 2 |  | 17 | use constant MINUTE => 60; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 138 |  | 
| 89 | 2 |  |  | 2 |  | 12 | use constant HOUR => 3600; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 123 |  | 
| 90 | 2 |  |  | 2 |  | 12 | use constant DAY  => 24 * HOUR; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 105 |  | 
| 91 | 2 |  |  | 2 |  | 12 | use constant YEAR => 365 * DAY; | 
|  | 2 |  |  |  |  | 8 |  | 
|  | 2 |  |  |  |  | 1573 |  | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | sub _separate { | 
| 94 |  |  |  |  |  |  | # Breakdown of seconds into units, starting with the most significant | 
| 95 |  |  |  |  |  |  |  | 
| 96 | 226 |  |  | 226 |  | 328 | my $remainder = abs $_[0]; # remainder | 
| 97 | 226 |  |  |  |  | 317 | my $this; # scratch | 
| 98 |  |  |  |  |  |  | my @wheel; # retval | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | # Years: | 
| 101 | 226 |  |  |  |  | 456 | $this = int($remainder / (365 * 24 * 60 * 60)); | 
| 102 | 226 |  |  |  |  | 499 | push @wheel, ['year', $this, 1_000_000_000]; | 
| 103 | 226 |  |  |  |  | 402 | $remainder -= $this * (365 * 24 * 60 * 60); | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | # Days: | 
| 106 | 226 |  |  |  |  | 309 | $this = int($remainder / (24 * 60 * 60)); | 
| 107 | 226 |  |  |  |  | 372 | push @wheel, ['day', $this, 365]; | 
| 108 | 226 |  |  |  |  | 327 | $remainder -= $this * (24 * 60 * 60); | 
| 109 |  |  |  |  |  |  |  | 
| 110 |  |  |  |  |  |  | # Hours: | 
| 111 | 226 |  |  |  |  | 323 | $this = int($remainder / (60 * 60)); | 
| 112 | 226 |  |  |  |  | 375 | push @wheel, ['hour', $this, 24]; | 
| 113 | 226 |  |  |  |  | 321 | $remainder -= $this * (60 * 60); | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  | # Minutes: | 
| 116 | 226 |  |  |  |  | 324 | $this = int($remainder / 60); | 
| 117 | 226 |  |  |  |  | 351 | push @wheel, ['minute', $this, 60]; | 
| 118 | 226 |  |  |  |  | 314 | $remainder -= $this * 60; | 
| 119 |  |  |  |  |  |  |  | 
| 120 | 226 |  |  |  |  | 351 | push @wheel, ['second', int($remainder), 60]; | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | # Thanks to Steven Haryanto (http://search.cpan.org/~sharyanto/) for the basis of this change. | 
| 123 | 226 | 100 |  |  |  | 462 | if ($MILLISECOND) { | 
| 124 | 18 |  |  |  |  | 26 | $remainder -= int($remainder); | 
| 125 | 18 |  |  |  |  | 118 | push @wheel, ['millisecond', sprintf("%0.f", $remainder * 1000), 1000]; | 
| 126 |  |  |  |  |  |  | } | 
| 127 |  |  |  |  |  |  |  | 
| 128 | 226 |  |  |  |  | 578 | return @wheel; | 
| 129 |  |  |  |  |  |  | } | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 
| 132 |  |  |  |  |  |  | sub _approximate { | 
| 133 |  |  |  |  |  |  | # Now nudge the wheels into an acceptably (im)precise configuration | 
| 134 | 110 |  |  | 110 |  | 225 | my($precision, @wheel) = @_; | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  | Fix: | 
| 137 |  |  |  |  |  |  | { | 
| 138 |  |  |  |  |  |  | # Constraints for leaving this block: | 
| 139 |  |  |  |  |  |  | #  1) number of nonzero wheels must be <= $precision | 
| 140 |  |  |  |  |  |  | #  2) no wheels can be improperly expressed (like having "60" for mins) | 
| 141 |  |  |  |  |  |  |  | 
| 142 | 110 |  |  |  |  | 144 | my $nonzero_count = 0; | 
|  | 175 |  |  |  |  | 228 |  | 
| 143 | 175 |  |  |  |  | 230 | my $improperly_expressed; | 
| 144 |  |  |  |  |  |  |  | 
| 145 | 175 |  |  |  |  | 198 | DEBUG and print join ' ', '#', (map "${$_}[1] ${$_}[0]",  @wheel), "\n"; | 
| 146 | 175 |  |  |  |  | 339 | for(my $i = 0; $i < @wheel; $i++) { | 
| 147 | 864 |  |  |  |  | 1189 | my $this = $wheel[$i]; | 
| 148 | 864 | 100 |  |  |  | 1753 | next if $this->[1] == 0; # Zeros require no attention. | 
| 149 | 386 |  |  |  |  | 459 | ++$nonzero_count; | 
| 150 | 386 | 100 |  |  |  | 648 | next if $i == 0; # the years wheel is never improper or over any limit; skip | 
| 151 |  |  |  |  |  |  |  | 
| 152 | 324 | 100 |  |  |  | 820 | if($nonzero_count > $precision) { | 
|  |  | 100 |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | # This is one nonzero wheel too many! | 
| 154 | 45 |  |  |  |  | 59 | DEBUG and print '', $this->[0], " is one nonzero too many!\n"; | 
| 155 |  |  |  |  |  |  |  | 
| 156 |  |  |  |  |  |  | # Incr previous wheel if we're big enough: | 
| 157 | 45 | 100 |  |  |  | 120 | if($this->[1] >= ($this->[-1] / 2)) { | 
| 158 | 26 |  |  |  |  | 33 | DEBUG and printf "incrementing %s from %s to %s\n", | 
| 159 |  |  |  |  |  |  | $wheel[$i-1][0], $wheel[$i-1][1], 1 + $wheel[$i-1][1], ; | 
| 160 | 26 |  |  |  |  | 40 | ++$wheel[$i-1][1]; | 
| 161 |  |  |  |  |  |  | } | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | # Reset this and subsequent wheels to 0: | 
| 164 | 45 |  |  |  |  | 101 | for(my $j = $i; $j < @wheel; $j++) { $wheel[$j][1] = 0 } | 
|  | 76 |  |  |  |  | 152 |  | 
| 165 | 45 |  |  |  |  | 84 | redo Fix; # Start over. | 
| 166 |  |  |  |  |  |  | } elsif($this->[1] >= $this->[-1]) { | 
| 167 |  |  |  |  |  |  | # It's an improperly expressed wheel.  (Like "60" on the mins wheel) | 
| 168 | 20 |  |  |  |  | 29 | $improperly_expressed = $i; | 
| 169 | 20 |  |  |  |  | 426 | DEBUG and print '', $this->[0], ' (', $this->[1], | 
| 170 |  |  |  |  |  |  | ") is improper!\n"; | 
| 171 |  |  |  |  |  |  | } | 
| 172 |  |  |  |  |  |  | } | 
| 173 |  |  |  |  |  |  |  | 
| 174 | 130 | 100 |  |  |  | 248 | if(defined $improperly_expressed) { | 
| 175 |  |  |  |  |  |  | # Only fix the least-significant improperly expressed wheel (at a time). | 
| 176 | 20 |  |  |  |  | 27 | DEBUG and printf "incrementing %s from %s to %s\n", | 
| 177 |  |  |  |  |  |  | $wheel[$improperly_expressed-1][0], $wheel[$improperly_expressed-1][1], | 
| 178 |  |  |  |  |  |  | 1 + $wheel[$improperly_expressed-1][1], ; | 
| 179 | 20 |  |  |  |  | 25 | ++$wheel[ $improperly_expressed - 1][1]; | 
| 180 | 20 |  |  |  |  | 27 | $wheel[ $improperly_expressed][1] = 0; | 
| 181 |  |  |  |  |  |  | # We never have a "150" in the minutes slot -- if it's improper, | 
| 182 |  |  |  |  |  |  | #  it's only by having been rounded up to the limit. | 
| 183 | 20 |  |  |  |  | 38 | redo Fix; # Start over. | 
| 184 |  |  |  |  |  |  | } | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | # Otherwise there's not too many nonzero wheels, and there's no | 
| 187 |  |  |  |  |  |  | #  improperly expressed wheels, so fall thru... | 
| 188 |  |  |  |  |  |  | } | 
| 189 |  |  |  |  |  |  |  | 
| 190 | 110 |  |  |  |  | 260 | return @wheel; | 
| 191 |  |  |  |  |  |  | } | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 
| 194 |  |  |  |  |  |  | sub _render { | 
| 195 |  |  |  |  |  |  | # Make it into English | 
| 196 |  |  |  |  |  |  |  | 
| 197 | 226 |  |  | 226 |  | 379 | my $direction = shift @_; | 
| 198 |  |  |  |  |  |  | my @wheel = map | 
| 199 | 226 |  |  |  |  | 395 | {; | 
| 200 | 1148 | 100 |  |  |  | 1996 | (  $_->[1] == 0) ? ()  # zero wheels | 
|  |  | 100 |  |  |  |  |  | 
| 201 | 198 |  |  |  |  | 367 | : ($_->[1] == 1) ? "${$_}[1] ${$_}[0]"  # singular | 
|  | 198 |  |  |  |  | 413 |  | 
| 202 | 321 |  |  |  |  | 584 | :                  "${$_}[1] ${$_}[0]s" # plural | 
|  | 321 |  |  |  |  | 746 |  | 
| 203 |  |  |  |  |  |  | } | 
| 204 |  |  |  |  |  |  | @_ | 
| 205 |  |  |  |  |  |  | ; | 
| 206 | 226 | 100 |  |  |  | 477 | return "just now" unless @wheel; # sanity | 
| 207 | 225 |  |  |  |  | 416 | $wheel[-1] .= $direction; | 
| 208 | 225 | 100 |  |  |  | 622 | return $wheel[0] if @wheel == 1; | 
| 209 | 160 | 100 |  |  |  | 534 | return "$wheel[0] and $wheel[1]" if @wheel == 2; | 
| 210 | 89 |  |  |  |  | 160 | $wheel[-1] = "and $wheel[-1]"; | 
| 211 | 89 |  |  |  |  | 425 | return join q{, }, @wheel; | 
| 212 |  |  |  |  |  |  | } | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 
| 215 |  |  |  |  |  |  | 1; | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | __END__ |