Hi,
this is my suggestion... The appended patch adds two standard arguments: 'units' and 'precision':
The argument 'units' allows to specify which units will be passed to DateTime::Format's in_unit() method. Example:
my $fmt = DateTime::Format::Human::Duration->new();
my $d = DateTime::Duration->new(...);
my $s = $fmt->format_duration($d, 'units' => [qw/years months days/] );
$s now is a string that includes years, months, and days only.
'precision' keeps the given or default units but removes all units above the given time resolution. So if you want to
see the duration in minute resolution only (no seconds and nanoseconds):
$fmt->format_duration($d, 'precision' => 'days');
$s is now '1 year, 7 months, 2 weeks, and 2 days'.
I've also added some documentation (POD). See there for more details. Or take a look at the code.
Stephan
Am Mi 11. Apr 2012, 13:50:36, MSTRAT schrieb:
Show quoted text> I'm currently using some crude workarounds elsewhere to achieve this,
> but perhaps it would be
> useful to be able to control which units are used in the formatted
> strings.
>
> Two specific scenarios to consider:
> * Resolution: the max number of units to use. e.g. '1y 2mo 23d 14h'
> with a resolution of 2 would
> yield '1y 2mo'
> * smallest unit: e.g. with a smallest unit of 'month' '1y 2mo 23d'
> becomes '1y 2mo'
>
> My first reaction is to add these as options that can be passed to
> format_duration() and
> format_duration_between()
>
> Any thoughts or opposition?
diff -u --recursive DateTime/Format/Human/Duration.pm /home/austest/dev/SMF/lib/DateTime/Format/Human/Duration.pm
--- DateTime/Format/Human/Duration.pm 2012-07-05 15:36:12.000000000 +0200
+++ /home/austest/dev/SMF/lib/DateTime/Format/Human/Duration.pm 2012-07-12 10:53:09.691591209 +0200
@@ -6,6 +6,21 @@
our $VERSION = '0.51';
+use constant {
+ PLMAP => {
+ 'years' => [ 'year', 'years' ],
+ 'months' => [ 'month', 'months' ],
+ 'weeks' => [ 'week', 'weeks' ],
+ 'days' => [ 'day', 'days' ],
+ 'hours' => [ 'hour', 'hours' ],
+ 'minutes' => [ 'minute', 'minutes' ],
+ 'seconds' => [ 'second', 'seconds' ],
+ 'nanoseconds' => [ 'nanosecond', 'nanoseconds' ],
+ },
+};
+
+use Carp qw/croak/;
+
sub new {
bless { 'locale_cache' => {} }, 'DateTime::Format::Human::Duration';
}
@@ -23,8 +38,19 @@
sub format_duration {
my ($span, $duration, %args) = @_;
+
+ my @units = $args{'units'} ? @{ $args{'units'} } : qw(years months weeks days hours minutes seconds nanoseconds);
+
+ if ($args{'precision'}) {
+ # Reduce time resolution to requested precision
+ for (my $i = 0; $i < scalar(@units); $i++) {
+ next unless ($units[$i] eq $args{'precision'});
+ splice(@units, $i + 1);
+ }
+ croak('Useless precision') unless (@units);
+ }
- my @raw = $duration->in_units( qw(years months weeks days hours minutes seconds nanoseconds) );
+ my @raw = $duration->in_units( @units );
my @n = map { abs($_) } @raw; # no negative numbers
my $say = '';
@@ -86,17 +112,12 @@
# reorder @n use if appropriate for locale
# @n has been pass through abs() so that its never negative
- my @parts = grep { $_ } (
- $n[0] ? ( $n[0]. ' ' . ($n[0] == 1 ? $setup->{'year'} : $setup->{'years'})) : '',
- $n[1] ? ( $n[1] . ' ' .($n[1] == 1 ? $setup->{'month'} : $setup->{'months'})) : '',
- $n[2] ? ( $n[2] . ' ' .($n[2] == 1 ? $setup->{'week'} : $setup->{'weeks'})) : '',
- $n[3] ? ( $n[3] . ' ' .($n[3] == 1 ? $setup->{'day'} : $setup->{'days'})) : '',
- $n[4] ? ( $n[4] . ' ' .($n[4] == 1 ? $setup->{'hour'} : $setup->{'hours'})) : '',
- $n[5] ? ( $n[5] . ' ' .($n[5] == 1 ? $setup->{'minute'} : $setup->{'minutes'})) : '',
- $n[6] ? ( $n[6] . ' ' .($n[6] == 1 ? $setup->{'second'} : $setup->{'seconds'})) : '',
- $n[7] ? ( $n[7] . ' ' .($n[7] == 1 ? $setup->{'nanosecond'} : $setup->{'nanoseconds'})) : '',
- );
-
+ my @parts;
+ for (my $i = 0; $i < scalar(@units); $i++) {
+ next unless ($n[$i]);
+ push(@parts, $n[$i] . ' ' . $setup->{ PLMAP->{ $units[$i] }->[ $n[$i] == 1 ? 0 : 1 ] });
+ }
+
my $no_time = exists $args{'no_time'} ? $args{'no_time'} : $setup->{'no_time'};
return $no_time if !@parts;
@@ -192,6 +213,40 @@
=back
+=item 3 Time Resolution and Units
+
+=over 4
+
+=item * Units
+
+Specify units to format duration with. Arguments will be passed to DateTime::Format's in_unit() method.
+
+Example:
+
+ my $fmt = DateTime::Format::Human::Duration->new();
+ my $d = DateTime::Duration->new(...);
+
+ my $s = $fmt->format_duration($d, 'units' => [qw/years months days/] );
+ # $s == '1 year, 7 months, and 16 days'
+
+=item * precision
+
+By default, the duration will be formatted using nanosecond resolution. Resolution can be reduced by passing
+'years', 'months', 'weeks', 'days', 'hours', 'minutes', or 'seconds' to the 'precision' argument.
+
+Example:
+
+ my $fmt = DateTime::Format::Human::Duration->new();
+ my $d = DateTime::Duration->new(...);
+
+ my $a = $fmt->format_duration($d);
+ # $a == '1 year, 7 months, 2 weeks, 2 days, 9 hours, 43 minutes, and 15 seconds'
+
+ my $b = $fmt->format_duration($d, 'precision' => 'days');
+ # $b == '1 year, 7 months, 2 weeks, and 2 days'
+
+=back
+
If duration is baseless (IE ambiguouse) then 'past' and 'future' is used based on if $dur->in_units has negatives or not.
Also by nature it's not split into type groups: