From 8d781b46d24e4aea478f2c726470d01c2d29d947 Mon Sep 17 00:00:00 2001 From: "Mark A. Stratman" Date: Tue, 10 Apr 2012 18:45:20 -0500 Subject: [PATCH] Initial commit - version 0.0.1 --- Build.PL | 17 + Changes | 5 + MANIFEST | 16 + META.yml | 12 + Makefile.PL | 17 + README | 33 ++ lib/DateTime/Format/Human/Duration.pm | 430 ++++++++++++++++++ lib/DateTime/Format/Human/Duration/Locale.pm | 88 ++++ .../Format/Human/Duration/Locale/es.pm | 43 ++ .../Format/Human/Duration/Locale/fr.pm | 43 ++ .../Format/Human/Duration/Locale/pt.pm | 38 ++ t/00.load.t | 7 + t/01.methods.t | 65 +++ t/perlcritic.t | 7 + t/pod-coverage.t | 10 + t/pod.t | 6 + 16 files changed, 837 insertions(+) create mode 100644 Build.PL create mode 100644 Changes create mode 100644 MANIFEST create mode 100644 META.yml create mode 100644 Makefile.PL create mode 100644 README create mode 100644 lib/DateTime/Format/Human/Duration.pm create mode 100644 lib/DateTime/Format/Human/Duration/Locale.pm create mode 100644 lib/DateTime/Format/Human/Duration/Locale/es.pm create mode 100644 lib/DateTime/Format/Human/Duration/Locale/fr.pm create mode 100644 lib/DateTime/Format/Human/Duration/Locale/pt.pm create mode 100644 t/00.load.t create mode 100644 t/01.methods.t create mode 100644 t/perlcritic.t create mode 100644 t/pod-coverage.t create mode 100644 t/pod.t diff --git a/Build.PL b/Build.PL new file mode 100644 index 0000000..2e1c99b --- /dev/null +++ b/Build.PL @@ -0,0 +1,17 @@ +use strict; +use warnings; +use Module::Build; + +my $builder = Module::Build->new( + module_name => 'DateTime::Format::Human::Duration', + license => 'perl', + dist_author => 'Daniel Muey ', + dist_version_from => 'lib/DateTime/Format/Human/Duration.pm', + requires => { + 'Test::More' => 0, + 'version' => 0, + }, + add_to_cleanup => [ 'DateTime-Format-Human-Duration-*' ], +); + +$builder->create_build_script(); diff --git a/Changes b/Changes new file mode 100644 index 0000000..3c3926c --- /dev/null +++ b/Changes @@ -0,0 +1,5 @@ +Revision history for DateTime-Format-Human-Duration + +0.0.1 Sun Mar 16 10:54:08 2008 + Initial release. + diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..fe84053 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,16 @@ +Build.PL +Changes +MANIFEST +Makefile.PL +README +lib/DateTime/Format/Human/Duration.pm +lib/DateTime/Format/Human/Duration/Locale.pm +lib/DateTime/Format/Human/Duration/Locale/es.pm +lib/DateTime/Format/Human/Duration/Locale/fr.pm +lib/DateTime/Format/Human/Duration/Locale/pt.pm +t/00.load.t +t/01.methods.t +t/perlcritic.t +t/pod-coverage.t +t/pod.t +META.yml Module meta-data (added by MakeMaker) diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..67bd506 --- /dev/null +++ b/META.yml @@ -0,0 +1,12 @@ +# http://module-build.sourceforge.net/META-spec.html +#XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX# +name: DateTime-Format-Human-Duration +version: 0.0.1 +version_from: lib/DateTime/Format/Human/Duration.pm +installdirs: site +requires: + Test::More: 0 + version: 0 + +distribution_type: module +generated_by: ExtUtils::MakeMaker version 6.30 diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..4c2969a --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,17 @@ +use strict; +use warnings; +use ExtUtils::MakeMaker; + +WriteMakefile( + NAME => 'DateTime::Format::Human::Duration', + AUTHOR => 'Daniel Muey ', + VERSION_FROM => 'lib/DateTime/Format/Human/Duration.pm', + ABSTRACT_FROM => 'lib/DateTime/Format/Human/Duration.pm', + PL_FILES => {}, + PREREQ_PM => { + 'Test::More' => 0, + 'version' => 0, + }, + dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, + clean => { FILES => 'DateTime-Format-Human-Duration-*' }, +); diff --git a/README b/README new file mode 100644 index 0000000..13feb9e --- /dev/null +++ b/README @@ -0,0 +1,33 @@ +DateTime-Format-Human-Duration version 0.0.1 + +DOCUMENTATION + +See POD for documentation. + +INSTALLATION + +To install this module, run the following commands: + + perl Makefile.PL + make + make test + make install + +Alternatively, to install with Module::Build, you can use the following commands: + + perl Build.PL + ./Build + ./Build test + ./Build install + +DEPENDENCIES + +See DEPENDENCIES section in POD, 'requires' key in Build.PL, +or 'PREREQ_PM' key in Makefile.PL + +COPYRIGHT AND LICENCE + +Copyright (C) 2008, Daniel Muey + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. diff --git a/lib/DateTime/Format/Human/Duration.pm b/lib/DateTime/Format/Human/Duration.pm new file mode 100644 index 0000000..c88e1f8 --- /dev/null +++ b/lib/DateTime/Format/Human/Duration.pm @@ -0,0 +1,430 @@ +package DateTime::Format::Human::Duration; + +use warnings; +use strict; +require DateTime::Format::Human::Duration::Locale; + +use version; our $VERSION = qv('0.0.1'); + +sub new { + bless { 'locale_cache' => {} }, 'DateTime::Format::Human::Duration'; +} + +sub format_duration_between { + my ($span, $dt, $dtb, %args) = @_; + my $dur = $dt - $dtb; + + if (!exists $args{'locale'}) { + $args{'locale'} = $dt->{'locale'}{'id'}; + } + + return $span->format_duration($dur, %args); +} + +sub format_duration { + my ($span, $duration, %args) = @_; + + my @raw = $duration->in_units( qw(years months weeks days hours minutes seconds nanoseconds) ); + my @n = map { abs($_) } @raw; # no negative numbers + my $say = ''; + + # $dta - $dtb: + # if dta < dtb means past -> future (Duration units will have negatives) + # else its either this absolute instant (no_time) or the past + if ( grep { $_ < 0 } @raw ) { + if ( exists $args{'future'} ) { + $say = $args{'future'} + } + } + else { + if ( exists $args{'past'} ) { + $say = $args{'past'} + } + } + + #### + ## this is essencially the hashref that is returned from DateTime::Format::Human::Duration::en::get_human_span_hashref() : # + #### + my $setup = { + 'no_oxford_comma' => 0, + 'no_time' => 'no time', # The wait will be $formatted_duration + 'and' => 'and', + 'year' => 'year', + 'years' => 'years', + 'month' => 'month', + 'months' => 'months', + 'week' => 'week', + 'weeks' => 'weeks', + 'day' => 'day', + 'days' => 'days', + 'hour' => 'hour', + 'hours' => 'hours', + 'minute' => 'minute', + 'minutes' => 'minutes', + 'second' => 'second', + 'seconds' => 'seconds', + 'nanosecond' => 'nanosecond', + 'nanoseconds' => 'nanoseconds', + }; + + my $locale = DateTime::Format::Human::Duration::Locale::calc_locale($span, $args{'locale'}); + + if($locale) { + if ( ref $locale eq 'HASH' ) { + %{ $setup } = ( + %{ $setup }, + %{ $locale }, + ); + } + elsif ( ref $locale eq 'CODE') { + return $locale->( @n, \%args ); + } + } + + # this is what a locale's get_human_span_from_units_array() should do: + # my (@n, $args_hr) = @_; + + # 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 $no_time = exists $args{'no_time'} ? $args{'no_time'} : $setup->{'no_time'}; + return $no_time if !@parts; + + my $last = @parts > 1 ? pop(@parts): ''; + + ## We want to use the so-called Oxford comma to avoid ambiguity. + ## For that reason we make locale's specifically tell us they do not want it. + my $string = $setup->{'no_oxford_comma'} + ? join(', ', @parts) . ($last ? " $setup->{'and'} $last" : '') + : join(', ', @parts) . (@parts > 1 ? ',' : '') . ($last ? " $setup->{'and'} $last" : '') + ; + + if ( $say ) { + $string = $say =~ m{%s} ? sprintf($say, $string): "$say $string"; + } + + return $string; +} + +1; + +__END__ + +=head1 NAME + +DateTime::Format::Human::Duration - Get a locale specific string describing the span of a given duration + +=head1 VERSION + +This document describes DateTime::Format::Human::Duration version 0.0.1 + +=head1 SYNOPSIS + + use DateTime; + use DateTime::Format::Human::Duration + + my $span = DateTime::Format::Human::Duration->new(); + my $dur = $dta - $dtb; + print $span->format_duration($dur); # 1 year, 2 months, 3 minutes, and 1 second + + print $span->format_duration_between($dta, $dtb); # 1 year, 2 months, 3 minutes, and 1 second + +=head1 DESCRIPTION + +Get a localized string representing the duration. + +For example: + + 1 second + 2 minutes and 3 seconds + 3 weeks, 1 day, and 5 seconds + 4 years, 1 month, 2 days, 6 minutes, 1 second, and 345000028 nanoseconds + +=head1 INTERFACE + +=head2 new() + +Create span object, no args + +=head2 format_duration() + +First argument is a DateTime::Duration object + +After that you can optionally pass some 'standard args' as a hash as described below + +=head2 format_duration_between() + +First two args are DateTime objects + +After that you can optionally pass some 'standard args' as a hash as described below + +=head2 standard args + +=over 4 + +=item 1 'locale' + +locale of the $dt object will be used if you do not specify this + +Valid values are a string of the locale (E.g 'fr'), a DateTime object, or a DateTime object's 'locale' key. + +=item 2 since we're working with 2 datetime objects of known points we can have past and future tenses. + +=over 4 + +=item * past + +String to use if duration is past tense. Can have a sprintf '%s' or else is prepended with a trailing space. + +=item * future + +String to use if duration is future tense. Can have a sprintf '%s' or else is prepended with a trailing space. + +=item * no_time + +Override the 'no_time' in the locale hash. + +=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: + +An example is + + DateTime::Duration->new('seconds'=> 62) + +Will result in '62 seconds' not '1 minute and 2 seconds' + +For more sane results always be specific by using 2 datetime object to get a duration object + +=back + + print $dt->format_duration_between( + $dta, + $dtb, + 'past' => 'Your account expired %s ago.', + 'future' => 'Your account expires in %s.', + 'no_time'=> 'Your account just expired.', + ); + +This facilitates, for example, this L vernacular which becomes: + + 'Your account [duration,_1,_2,expired %s ago,expires in,just expired].' => '[Votre compte [duration,_1,_2,a expiré il ya,expire dans,vient d'expirer].' + +=head1 LOCALIZATION + +Localization is provided by the included DateTime::Format::Human::Duration::Locale modules. + +Included are DateTime::Format::Human::Duration::Locale::es, DateTime::Format::Human::Duration::Locale::fr, DateTime::Format::Human::Duration::Locale::pt + +More will be included as time permits/folks volunteer/CLDR becomes an option + +They are setup this way: + +DateTime::Format::Human::Duration::Locale::XYZ where 'XYZ' is the ISO code of DateTime::Locale + +It can have one of 2 functions used in this order: + +=over 4 + +=item get_human_span_from_units_array() + +Try to use get_human_span_hashref() if the locale is disposed to it since its much easier... That said: + +Takes the arguments as described in the example below, should return the localized "span" string. + + sub get_human_span_from_units_array { + my ($years, $months, $weeks, $days, $hours, $minutes, $seconds, $nanoseconds, $args_hr) = @_; # note: has no negative numbers + ... + return $string; # 1 year, 2days, 4 hours, and 17 minutes + } + +=item get_human_span_hashref() + +Takes no arguments, should return a hashref of this structure: + + sub get_human_span_hashref { + return { + 'no_oxford_comma' => 1, + 'no_time' => 'pas le temps', + 'and' => 'et', + 'year' => 'an', + 'years' => 'ans', + 'month' => 'mois', + 'months' => 'mois', + 'week' => 'semaine', + 'weeks' => 'semaines', + 'day' => 'jour', + 'days' => 'jours', + 'hour' => 'heure', + 'hours' => 'heures', + 'minute' => 'minute', + 'minutes' => 'minutes', + 'second' => 'seconde', + 'seconds' => 'seconds', + 'nanosecond' => 'nanoseconde', + 'nanoseconds' => 'nanosecondes', + }; + } + +=back + +=head1 LOCALIZATION of DateTime::Format modules + +L does an excellent job at implementing localization. Often L based class's either don't support localization or they implement it haphazardly and inconsistently. + +With this module I hope to model a localization scheme that is inline with L and is consistent and reuseable between based classes. + +The idea is to determine the locale to use based on a DateTime object. + +XYZ::Locale should handle looking up (and caching if appropriate) the locale and loading the necessary locale module XYZ::Locale::fr + +The specific locale module holds the data and possibly logic neccesary to do what XYZ does in the vernacular of the given locale. + +=head2 TODO + +Eventually the generic logic will be re-broken out into its own module for re-use by your class and I'll have more detailed POD about how to do it. + +In the meantime if you're interested please contact me and I'd be happy to help and/or expediate this TODO. + +Also, Dave Rolksy has mentioned to me that this sort of locale data might be appropriate for DateTime::Locale directly from CLDR. If that happens this module will be changed to use that if possible. + +=head1 FAQ + +=head2 Why would I want to use this? + +So you can localize your application's output of time periods without having to do a lot of logic each time you wanted to say it. + +L has/will have a duration() bracket notation method which prompted this module's existence + +duration() was prompted by its datetime() brother, all of which uses the most excellent DateTime project! + +=head2 Why did my duration say '62 seconds' instead of '1 minute and 2 seconds' + +Because you used an ambiguous duration (one without a base) so there is no way to +apply date math and accurately represent the number of each given item in that +duration since it may or may not span leap-[second, days, years, etc..] + +In other words do this (so that your duration can be specifically calculated): + + $dtb = $dta->clone->add('seconds'=> 62); + my $duration = $dta - $dtb; # has a base, its not ambiguous + print $span->format_duration($duration); # 1 minutes and 2 seconds + +not this: + + my $duration = DateTime::Duration->new('seconds'=> 62); # no base, it is ambiguous + print $span->format_duration($duration); # 62 seconds + +Note L(), does not suffer from this since we're using a specific DateTime object already. + + print $span->format_duration_between( $dt, $dt->clone()->add('seconds'=> 62) ); # 1 minute and 2 seconds + +=head2 Why do you put a comma before the 'and' in a group of more than 2 items? + +We want to use the so-called Oxford comma to avoid ambiguity. + +=head2 My DateTime::Format::Human::Duration::Locale::XX still outputs in English! + +That is because it defined neither the get_human_span_hashref() or the get_human_span_from_units_array() functions + +It must define one of them or defaults are used. + +=head2 Why didn't you just use 'DateTime::Format::Duration' + +Essencially DateTime::Format::Duration is an object representing a single strftime() type string to apply to any given duration. This is not flexible enough for the intent of this module. + +DateTime::Format::Duration is not a bad module its just for a different purpose than DateTime::Format::Human::Duration + +=over 4 + +=item * It was not localizable + +You either got '2 days' or '1 days' which a) forces it to be in English and b) doesn't even make sense in English. + +You could get around that by adding logic each time you wanted to call it but that is just messy. + +=item * Had to keep an item even if it was zero + +If 'days' was in there you got '0 days', we only want items with a value to show. + +That'd also require a lot of logic each time you wanted to call which is again messy. + +=item * This module has no need for reparsing output back into an object + +Since the datetime info for 2 points in time are generally in a form easily rendered into a DateTime object it'd be silly to even attempt to store and parse the output of this module back into an object. + +Plus since it all depends on the locale it is in it'd be difficult. + +=back + +The purpose of DateTime::Format::Human::Duration was to generate a localized human language description of a duration without the caller needing to supply any logic. + +=head1 DIAGNOSTICS + +Throws no warnings or errors of its own + +=head1 CONFIGURATION AND ENVIRONMENT + +DateTime::Format::Human::Duration requires no configuration files or environment variables. + +=head1 DEPENDENCIES + +None. + +=head1 INCOMPATIBILITIES + +None reported. + +=head1 BUGS AND LIMITATIONS + +No bugs have been reported. + +Please report any bugs or feature requests to +C, or through the web interface at +L. + +=head1 AUTHOR + +Daniel Muey C<< >> + +=head1 LICENCE AND COPYRIGHT + +Copyright (c) 2008, Daniel Muey C<< >>. All rights reserved. + +This module is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. See L. + +=head1 DISCLAIMER OF WARRANTY + +BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE +ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH +YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL +NECESSARY SERVICING, REPAIR, OR CORRECTION. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE +LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, +OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE +THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. diff --git a/lib/DateTime/Format/Human/Duration/Locale.pm b/lib/DateTime/Format/Human/Duration/Locale.pm new file mode 100644 index 0000000..5ff20a3 --- /dev/null +++ b/lib/DateTime/Format/Human/Duration/Locale.pm @@ -0,0 +1,88 @@ +package DateTime::Format::Human::Duration::Locale; + +# require DateTime::Format::Locale; + +use strict; +use warnings; + +sub calc_locale { + my ($span, $loc) = @_; + + # DateTime::Format::Locale:: + my $final = determine_locale_from({ + 'base_object' => $span, + 'get_locale_from' => $loc, + 'locale_ns_path' => 'DateTime/Format/Human/Duration/Locale', # DateTime::Format::Human::Duration::Locale + }); + + if ($final) { + return $final if ref $final; # returned 'locale_cache' we created below + + my $ns = "DateTime::Format::Human::Duration::Locale::$final"; + if ( my $code_a = $ns->can('get_human_span_from_units_array') ) { + $span->{'locale_cache'}{ $final } = $code_a; + } + elsif ( my $code_b = $ns->can('get_human_span_hashref') ) { + $span->{'locale_cache'}{ $final } = $code_b->(); + } + + if ( exists $span->{'locale_cache'}{ $final } ) { + return $span->{'locale_cache'}{ $final }; + } + } + + return ''; +} + +# DateTime::Format::Locale:: +sub determine_locale_from { + my ($args_hr) = @_; + + return '' if !$args_hr->{'get_locale_from'}; + + if (ref $args_hr->{'get_locale_from'}) { + my $ns = ref($args_hr->{'get_locale_from'}); + + if (exists $args_hr->{'get_locale_from'}{'locale'}) { + $ns = exists $args_hr->{'get_locale_from'}{'locale'}{'id'} ? $args_hr->{'get_locale_from'}{'locale'}{'id'} : ref($args_hr->{'get_locale_from'}{'locale'}); + } + elsif ($ns =~ m{^DateTime::Locale::} && exists $args_hr->{'get_locale_from'}{'id'}) { + $ns = $args_hr->{'get_locale_from'}{'id'}; + } + ($args_hr->{'get_locale_from'}) = reverse split /::/, $ns; + } + + my ($short) = split(/[-_]+/,$args_hr->{'get_locale_from'}); + + my $final = ''; + my @try = $args_hr->{'get_locale_from'} eq $short ? ($args_hr->{'get_locale_from'}) : ($args_hr->{'get_locale_from'}, $short); + + NS: + for my $locale ( @try ) { + if ( exists $args_hr->{'base_object'}{'locale_cache'}{ $locale } ) { + if ( $args_hr->{'base_object'}{'locale_cache'}{ $locale } ) { + return $locale; + } + else { + next NS; + } + } + + $args_hr->{'locale_ns_path'} =~ s{/$}{}; + my $path = "$args_hr->{'locale_ns_path'}/$locale\.pm"; + + if( exists $INC{$path} || eval { $args_hr->{'loads'}{$locale}++; require $path } ) { + $final = $locale; + $args_hr->{'base_object'}{'locale_cache'}{ $locale } = 1; + last NS; + } + else { + push @{$args_hr->{'errors'}{$locale}}, $@; + $args_hr->{'base_object'}{'locale_cache'}{ $locale } = ''; + } + } + + return $final; +} + +1; \ No newline at end of file diff --git a/lib/DateTime/Format/Human/Duration/Locale/es.pm b/lib/DateTime/Format/Human/Duration/Locale/es.pm new file mode 100644 index 0000000..d8b4445 --- /dev/null +++ b/lib/DateTime/Format/Human/Duration/Locale/es.pm @@ -0,0 +1,43 @@ +package DateTime::Format::Human::Duration::Locale::es; + +use strict; +use warnings; + +# Note to self: change 01.methods.t use of 'es' if this hashref changes or is removed + +# 1 year, 1 month, 1 week, 1 day, 1 hour, 1 minute, 1 second, and 1 nanosecond +# 2 years, 2 months, 2 weeks, 2 days, 2 hours, 2 minutes, 2 seconds, and 2 nanoseconds + +sub get_human_span_hashref { + return { + 'no_oxford_comma' => 0, + 'no_time' => 'no hay tiempo', + 'and' => 'y', + 'year' => 'año', + 'years' => 'años', + 'month' => 'mes', + 'months' => 'meses', + 'week' => 'semana', + 'weeks' => 'semanas', + 'day' => 'día', + 'days' => 'días', + 'hour' => 'hora', + 'hours' => 'horas', + 'minute' => 'minuto', + 'minutes' => 'minutos', + 'second' => 'segundo', + 'seconds' => 'segundos', + 'nanosecond' => 'nanosegundo', + 'nanoseconds' => 'nanosegundos', + }; +} + +# get_human_span_from_units_array() is used instead of get_human_span_hashref() if get_human_span_from_units_array() exists +# +# sub get_human_span_from_units_array { +# my ($years, $months, $weeks, $days, $hours, $minutes, $seconds, $nanoseconds, $args_hr) = @_; # note: has no negative numbers +# ... +# return $string; # 1 year, 2days, 4 hours, and 17 minutes +# } + +1; \ No newline at end of file diff --git a/lib/DateTime/Format/Human/Duration/Locale/fr.pm b/lib/DateTime/Format/Human/Duration/Locale/fr.pm new file mode 100644 index 0000000..c3ef4c5 --- /dev/null +++ b/lib/DateTime/Format/Human/Duration/Locale/fr.pm @@ -0,0 +1,43 @@ +package DateTime::Format::Human::Duration::Locale::fr; + +use strict; +use warnings; + +# Note to self: change 01.methods.t use of 'fr' if this hashref changes or is removed + +# 1 year, 1 month, 1 week, 1 day, 1 hour, 1 minute, 1 second, and 1 nanosecond +# 2 years, 2 months, 2 weeks, 2 days, 2 hours, 2 minutes, 2 seconds, and 2 nanoseconds + +sub get_human_span_hashref { + return { + 'no_oxford_comma' => 1, + 'no_time' => 'pas le temps', + 'and' => 'et', + 'year' => 'an', + 'years' => 'ans', + 'month' => 'mois', + 'months' => 'mois', + 'week' => 'semaine', + 'weeks' => 'semaines', + 'day' => 'jour', + 'days' => 'jours', + 'hour' => 'heure', + 'hours' => 'heures', + 'minute' => 'minute', + 'minutes' => 'minutes', + 'second' => 'seconde', + 'seconds' => 'seconds', + 'nanosecond' => 'nanoseconde', + 'nanoseconds' => 'nanosecondes', + }; +} + +# get_human_span_from_units_array() is used instead of get_human_span_hashref() if get_human_span_from_units_array() exists +# +# sub get_human_span_from_units_array { +# my ($years, $months, $weeks, $days, $hours, $minutes, $seconds, $nanoseconds, $args_hr) = @_; # note: has no negative numbers +# ... +# return $string; # 1 year, 2days, 4 hours, and 17 minutes +# } + +1; \ No newline at end of file diff --git a/lib/DateTime/Format/Human/Duration/Locale/pt.pm b/lib/DateTime/Format/Human/Duration/Locale/pt.pm new file mode 100644 index 0000000..ddf650d --- /dev/null +++ b/lib/DateTime/Format/Human/Duration/Locale/pt.pm @@ -0,0 +1,38 @@ +package DateTime::Format::Human::Duration::Locale::pt; + +use strict; +use warnings; + +sub get_human_span_hashref { + return { + 'no_oxford_comma' => 0, + 'no_time' => 'nenhum momento', + 'and' => 'e', + 'year' => 'ano', + 'years' => 'anos', + 'month' => 'mês', + 'months' => 'meses', + 'week' => 'semana', + 'weeks' => 'semanas', + 'day' => 'dia', + 'days' => 'dias', + 'hour' => 'hora', + 'hours' => 'horas', + 'minute' => 'minuto', + 'minutes' => 'minutos', + 'second' => 'segundo', + 'seconds' => 'segundos', + 'nanosecond' => 'nanosegundo', # nanosecond ? + 'nanoseconds' => 'nanosegundos', # nanosegundos ? + }; +} + +# get_human_span_from_units_array() is used instead of get_human_span_hashref() if get_human_span_from_units_array() exists +# +# sub get_human_span_from_units_array { +# my ($years, $months, $weeks, $days, $hours, $minutes, $seconds, $nanoseconds, $args_hr) = @_; # note: has no negative numbers +# ... +# return $string; # 1 year, 2days, 4 hours, and 17 minutes +# } + +1; \ No newline at end of file diff --git a/t/00.load.t b/t/00.load.t new file mode 100644 index 0000000..7f445e1 --- /dev/null +++ b/t/00.load.t @@ -0,0 +1,7 @@ +use Test::More tests => 1; + +BEGIN { +use_ok( 'DateTime::Format::Human::Duration' ); +} + +diag( "Testing DateTime::Format::Human::Duration $DateTime::Format::Human::Duration::VERSION" ); diff --git a/t/01.methods.t b/t/01.methods.t new file mode 100644 index 0000000..7d8c801 --- /dev/null +++ b/t/01.methods.t @@ -0,0 +1,65 @@ +use Test::More tests => 22; +use lib '../lib'; + +BEGIN { + use_ok( 'DateTime::Format::Human::Duration' ); +} + +diag( "Testing DateTime::Format::Human::Duration $DateTime::Format::Human::Duration::VERSION" ); + +# plan skip_all => 'DateTime required for creating DateTime object and durations' if $@; +# That fails under Test::More 0.70 like so: +# You tried to plan twice at t/01.methods.t line 11. +# Looks like you planned 22 tests but only ran 1. +# Looks like your test died just after 1. + +SKIP: { + eval 'use DateTime'; + skip 'DateTime required for creating DateTime object and durations', 22 if $@; + + my $span = DateTime::Format::Human::Duration->new(); + ok(ref $span eq 'DateTime::Format::Human::Duration', 'Obj creation'); + my $time = time; + my $dua = DateTime->from_epoch( 'epoch' => $time ); + my $dub = DateTime->from_epoch( 'epoch' => ($time + 2), 'locale' => 'fr' ); + my $duc = DateTime->from_epoch( 'epoch' => ($time + 63) ); + my $dud = DateTime->from_epoch( 'epoch' => ($time + 3625.4455) ); + my $due = DateTime->from_epoch( 'epoch' => ($time + 23775453.345) ); + my $duf = DateTime->from_epoch( 'epoch' => ($time + 61) ); + + my $dura = $dua - $dua; + # my $durb = $dua - $dub; + my $durc = $dua - $dub; + my $durd = $dub - $dua; + my $dure = $dua - $duc; + my $durf = $dua - $dud; + my $durg = $dua - $due; + + ok( $span->format_duration($dura) eq 'no time', 'No difference w/ default no_time'); + ok( $span->format_duration($dura, 'no_time' => 'absolutely no time' ) eq 'absolutely no time', 'No difference w/ no_time'); + ok( $span->format_duration($dura, 'no_time' => '' ) eq '', 'No difference w/ empty no_time'); + ok( $span->format_duration($durc) eq '2 seconds', '1 value'); + ok( $span->format_duration_between($dub, $dua) eq '2 seconds', 'Reverse/Negative is still positive (not "no time")'); + ok( $span->format_duration_between($dua, $duf) eq '1 minute and 1 second', '2 (singular values)'); + ok( $span->format_duration($dure) eq '1 minute and 3 seconds', '2 values (mixed)' ); + ok( $span->format_duration($durf) eq '1 hour, 25 seconds, and 445499897 nanoseconds', '> 2 values (3)'); + + ok( $span->format_duration($durg) eq '9 months, 1 day, 4 hours, 17 minutes, 33 seconds, and 345000028 nanoseconds', '> 2 values (5)'); + + ok( $span->format_duration($durc, 'future' => 'Hello, You have %s left') eq 'Hello, You have 2 seconds left', 'string with %s'); + ok( $span->format_duration($durc, 'future' => 'You have') eq 'You have 2 seconds', 'string w/ out %s'); + ok( $span->format_duration_between($dua, $dub) eq '2 seconds', 'DateTime object method format_duration_between()'); + + ok( $span->format_duration_between($dua, $duc, 'past'=>'Was done %s ago.','future' => 'Will be done in %s.') eq 'Will be done in 1 minute and 3 seconds.','$a->format_duration_between($b): $a < $b = future'); + ok( $span->format_duration_between($duc, $dua, 'past'=>'Was done %s ago.','future' => 'Will be done in %s.') eq 'Was done 1 minute and 3 seconds ago.','$a->format_duration_between($b): $a > $b = past'); + + ok( $span->format_duration_between( $duc, $duc->clone()->add('seconds'=> 62) ) eq '1 minute and 2 seconds', 'clone exmple'); + ok( $span->format_duration( DateTime::Duration->new('seconds'=> 62) ) eq '62 seconds', 'Ambiguous duration (baseless)'); + + # test 'locale' key + ok( $span->format_duration($dure, 'locale' => 'fr') eq '1 minute et 3 seconds', 'locale key as string format_duration()'); + ok( $span->format_duration($dure, 'locale' => $dub) eq '1 minute et 3 seconds', 'locale key as $DateTime obj format_duration()'); + ok( $span->format_duration($dure, 'locale' => $dub->{'locale'}) eq '1 minute et 3 seconds', 'locale key as $DateTime->{\'locale\'} format_duration()'); + ok( $span->format_duration_between($dub, $duc) eq '1 minute et 1 seconde', 'Object\'s locale used in format_duration_between()'); + +}; \ No newline at end of file diff --git a/t/perlcritic.t b/t/perlcritic.t new file mode 100644 index 0000000..877477b --- /dev/null +++ b/t/perlcritic.t @@ -0,0 +1,7 @@ +#!perl -T + +use Test::More; +eval 'use Test::Perl::Critic'; +plan skip_all => 'Test::Perl::Critic required for testing PBP compliance' if $@; +plan skip_all => q($ENV{'do_perl_critic_tests'} must be true to run these 'development only' tests) if !$ENV{'do_perl_critic_tests'}; +Test::Perl::Critic::all_critic_ok(); diff --git a/t/pod-coverage.t b/t/pod-coverage.t new file mode 100644 index 0000000..0976984 --- /dev/null +++ b/t/pod-coverage.t @@ -0,0 +1,10 @@ +#!perl -T + +use Test::More 'tests' => 1; +eval 'use Test::Pod::Coverage 1.04'; +plan skip_all => 'Test::Pod::Coverage 1.04 required for testing POD coverage' if $@; + +Test::Pod::Coverage::pod_coverage_ok( "DateTime::Format::Human::Duration", { 'trustme' => [qr/^(new)$/,], } ); + +# Locale.pm, es.pm, and fr.pm don;t have POD +# all_pod_coverage_ok(); \ No newline at end of file diff --git a/t/pod.t b/t/pod.t new file mode 100644 index 0000000..8e3e74a --- /dev/null +++ b/t/pod.t @@ -0,0 +1,6 @@ +#!perl -T + +use Test::More; +eval 'use Test::Pod 1.14'; +plan skip_all => 'Test::Pod 1.14 required for testing POD' if $@; +all_pod_files_ok();