Hi all,

this mail might be more or less OT, but nevertheless of some interest to
those of you faced with the problem of localizing webapps, more
precisely: of letting users choose between different languages.  Part of
this problem is localizing day and month names returned by
Time::Piece::strftime().  I came to deal with it while working on a
localization API for the CAP based CMS Krang (Hi to all you Krangers on
the list!).

As Time::Piece's strftime() uses the C function strftime() internally,
localization of the short and long day and month names as specified by
the format strings %a, %A, %b and %B is determined by the LC_TIME
environment variable setting, hence at compile time.  Even under
mod_cgi, let alone mod_perl, setting $ENV{LC_TIME} comes to late.  So I
looked for a way to do localization at runtime.  The result of this
effort is the module inlined here.  The included POD gives some more
detailed information, so I'll stop to repeat it here.  If you can think
of a better approach, please let me know.  Any comments and criticism is
very welcome.

Cheers,

-bodo

<code>
package Time::Piece::RuntimeLocalization;
use warnings;
use strict;

our $VERSION = '0.01';

=head1 NAME

Time::Piece::RuntimeLocalization - Runtime localization for Time::Piece

=head1 SYNOPSIS

 ### Order matters !
 use Time::Piece;                       # must be loaded first
 use Time::Piece::RuntimeLocalization;  # must be loaded afterwards

 ### Don't call Time::Piece::RuntimeLocalization directly
 ### Continue to use Time::Piece as usual
 my $date = localtime();

 # object setting
 $date->fullday_list(@fullday_names);   # set the full day names
 $date->fullmon_list(@fullmonth_names); # set the full month names

   or globally

 # class setting
 Time::Piece::fullday_list(@fullday_names);
 Time::Piece::fullmonth_list(@fullmonth_names);

All the standard methods of L<Time::Piece> are supported.

=head1 DESCRIPTION

Time::Piece allows to set the I<short> day and month names via the
methods

  * day_list(@short_day_names)
  * mon_list(@short_month_names)

There are however no equivalent methods to set the I<long> day and
month names, a gap filled by this module which provides

  * fullmon_list(@fullmonth_names)
  * fullday_list(@fullday_names)

However, the arrays set by the L<Time::Piece> methods day_list() and
mon_list() as well as those set by the new methods fullday_list() and
fullmonth_list() are only taken into account by the L<Time::Piece>
methods

  * monname()   # returns the short month name
  * month()     # dito
  * fullmonth() # returns the long month name

  * wdayname()  # returns the short day name
  * day()       # day
  * fullday()   # returns the long day name

They are I<not> considered by strftime(). So, even after setting the
day and month names via the *_list() methods, say, to French,

  $date->strftime('%a, %A, %b, %B');

will still return the day and month names localized to your I<LC_TIME> 
environment
variable setting (see man 3 L<strftime()>).  Localization, then, is determined
at compile time.  As far as I see, there is no way to set the
localization at run time. This however might be necessairy in web
applications which allow users to choose their preferred language.
Whatever their language setting, day and month names would always
show up localized according to the setting of LC_TIME.

To circumvent this shortcomming this module provides an alternate
version of strftime() which uses the short and long day and month names
set by the above mentionned four methods.  It does some typeglob hackery
to achieve this goal.  You therefore don't have to bother about this
module at all.  Just make sure to use() it I<after> use'ing Time::Piece
and continue to work with the normal Time::Piece interface.

=head1 METHODS

Both methods may be called as object and as class methods.

  * fullday_list(@full_day_names);
  * fullmon_list(@full_month_names);

=head1 NOTE ON PERFORMANCE

The localized version of strftime() comes at a cost.  On my PIII 500
MHz machine, it is six to seven times slower then pure
Time::Piece::strftime().  Here is a benchmark:

 Benchmark: timing 100000 iterations of Time::Piece...
 Time::Piece:  5 wallclock secs ( 6.34 usr +  0.06 sys =  6.40 CPU) @ 
15625.00/s (n=100000)

 Benchmark: timing 100000 iterations of Time::Piece::RuntimeLocalization...
 ::RuntimeLocalization: 37 wallclock secs (36.60 usr +  0.35 sys = 36.95 CPU) @ 
2706.36/s (n=100000)

=head1 AUTHOR

Bodo Schulze <[EMAIL PROTECTED]>

=head1 License

This module is free software, you may distribute it under the same terms
as Perl.

=head1 SEE ALSO

L<Time::Piece>, L<strftime>

=head1 BUGS

Unknown.

=cut

use constant 'c_sec' => 0;
use constant 'c_min' => 1;
use constant 'c_hour' => 2;
use constant 'c_mday' => 3;
use constant 'c_mon' => 4;
use constant 'c_year' => 5;
use constant 'c_wday' => 6;
use constant 'c_yday' => 7;
use constant 'c_isdst' => 8;
use constant 'c_epoch' => 9;
use constant 'c_islocal' => 10;

### The Time::Piece arrays @FULLDAY_LIST and @FULLMON_LIST are my()
### variables.  As we can't access from here, we have to replicate them.
our @FULLDAY_LIST = qw(Sunday TimePieceDefaultMonday Tuesday Wednesday Thursday 
Friday Saturday);
our @FULLMON_LIST = qw(January February March April May June July
                       TimePieceDefaultAugust September October November 
December);

### Set the array of full day names
sub fullday_list {
    shift if ref($_[0]) && $_[0]->isa('Time::Piece'); # strip first if called 
as a method
    my @old = @FULLDAY_LIST;
    if (@_) {
        @FULLDAY_LIST = @_;
    }
    return @old;
}

### Retrieve a full day name
sub fullday {
    my $time = shift;
    if (@_) {
        return $_[$time->[c_wday]];
    }
    elsif (@FULLDAY_LIST) {
        return $FULLDAY_LIST[$time->[c_wday]];
    }
    else {
        return $time->strftime('%A');
    }
}

### Set the array of full month names
sub fullmon_list {
    shift if ref($_[0]) && $_[0]->isa('Time::Piece'); # strip first if called 
as a method
    my @old = @FULLMON_LIST;
    if (@_) {
        @FULLMON_LIST = @_;
    }
    return @old;
}

### Retrieve a full month name
sub fullmonth {
    my $time = shift;
    if (@_) {
        return $_[$time->[c_mon]];
    }
    elsif (@FULLMON_LIST) {
        return $FULLMON_LIST[$time->[c_mon]];
    }
    else {
        return $time->strftime('%B');
    }
}

### The alternate strftime() version retrieving day and month names
### from their respective arrays
sub strftime {
    my $time = shift;
    my $format = @_ ? shift(@_) : "%a, %d %b %Y %H:%M:%S %Z";

    my $datetime;
    # Return the pattern along with the splitted pieces
    for my $piece (split /(%a|%A|%b|%B)/, $format) {
        next unless $piece;
        if ($piece eq '%a') {
            $datetime .= $time->day;        # retrieve from my(@DAY_LIST) in 
Time::Piece
        } elsif ($piece eq '%b') {
            $datetime .= $time->month;      # retrieve from my(@MON_LIST) in 
Time::Piece
        } elsif ($piece eq '%A') {
            $datetime .= $time->fullday;    # retrieve form our(@FULLDAY_LIST) 
here
        } elsif ($piece eq '%B') {
            $datetime .= $time->fullmonth;  # retrieve form our(@FULLMON_LIST) 
here
        } else {
            if (!defined $time->[c_wday]) {
                if ($time->[c_islocal]) {
                    $datetime .= _strftime($piece, 
CORE::localtime($time->epoch));
                } else {
                    $datetime .= _strftime($piece, CORE::gmtime($time->epoch));
                }
            }
            $datetime .= _strftime($piece, (@$time)[c_sec..c_isdst]);
        }
    }
    return $datetime;
}

### Typeglob hackery
# Undefine the Time::Piece methods we override
undef &Time::Piece::fullmonth;
undef &Time::Piece::fullday;
undef &Time::Piece::strftime;
# Alias our methods to Time::Piece
*Time::Piece::fullday_list = \&fullday_list;
*Time::Piece::fullmon_list = \&fullmon_list;
*Time::Piece::fullday   = \&fullday;
*Time::Piece::fullmonth = \&fullmonth;
*Time::Piece::strftime  = \&strftime;
# Alias Time::Piece::_strftime to our _strftime()
# for use in our strftime()
*_strftime = \&Time::Piece::_strftime;

1; </code>

---------------------------------------------------------------------
Web Archive:  http://www.mail-archive.com/[email protected]/
              http://marc.theaimsgroup.com/?l=cgiapp&r=1&w=2
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to