File Coverage

blib/lib/DateTime/Locale.pm
Criterion Covered Total %
statement 139 148 93.9
branch 57 76 75.0
condition 6 12 50.0
subroutine 21 23 91.3
pod 5 9 55.5
total 228 268 85.0


line stmt bran cond sub pod time code
1             package DateTime::Locale;
2              
3 15     15   11498209 use 5.008004;
  15         193  
4              
5 15     15   102 use strict;
  15         38  
  15         414  
6 15     15   91 use warnings;
  15         35  
  15         584  
7 15     15   7954 use namespace::autoclean;
  15         280742  
  15         67  
8              
9             our $VERSION = '1.39';
10              
11 15     15   20882 use DateTime::Locale::Data;
  15         999  
  15         1239  
12 15     15   7631 use DateTime::Locale::FromData;
  15         73  
  15         636  
13 15     15   114 use DateTime::Locale::Util qw( parse_locale_code );
  15         33  
  15         859  
14 15     15   102 use Params::ValidationCompiler 0.13 qw( validation_for );
  15         331  
  15         686  
15 15     15   7243 use Specio::Library::String;
  15         163183  
  15         166  
16              
17             my %Class;
18             my %DataForCode;
19             my %NameToCode;
20             my %NativeNameToCode;
21             my %UserDefinedAlias;
22              
23             my %LoadCache;
24              
25             sub register {
26 8     8 0 10136 my $class = shift;
27              
28 8         56 %LoadCache = ();
29              
30 8 100       28 if ( ref $_[0] ) {
31 2         9 $class->_register(%$_) foreach @_;
32             }
33             else {
34 6         23 $class->_register(@_);
35             }
36             }
37              
38             sub _register {
39 9     9   16 shift;
40 9         43 my %p = @_;
41              
42 9         19 my $id = $p{id};
43              
44 9 100       61 die q{'\@' or '=' are not allowed in locale ids}
45             if $id =~ /[\@=]/;
46              
47             die
48             "You cannot replace an existing locale ('$id') unless you also specify the 'replace' parameter as true\n"
49 7 50 33     43 if !delete $p{replace} && exists $DataForCode{$id};
50              
51             $p{native_language} = $p{en_language}
52 7 50       26 unless exists $p{native_language};
53              
54 7         15 my @en_pieces;
55             my @native_pieces;
56 7         17 foreach my $p (qw( language script territory variant )) {
57 28 100       78 push @en_pieces, $p{"en_$p"} if exists $p{"en_$p"};
58 28 100       73 push @native_pieces, $p{"native_$p"} if exists $p{"native_$p"};
59             }
60              
61 7         32 $p{en_complete_name} = join q{ }, @en_pieces;
62 7         16 $p{native_complete_name} = join q{ }, @native_pieces;
63              
64 7         28 $id =~ s/_/-/g;
65              
66 7         21 $DataForCode{$id} = \%p;
67              
68 7         18 $NameToCode{ $p{en_complete_name} } = $id;
69 7         16 $NativeNameToCode{ $p{native_complete_name} } = $id;
70              
71 7 50       52 $Class{$id} = $p{class} if defined exists $p{class};
72             }
73              
74             sub register_from_data {
75 1     1 1 18 shift;
76              
77 1         3 %LoadCache = ();
78              
79 1 50       15 my %p = ref $_[0] ? %{ $_[0] } : @_;
  0         0  
80              
81 1         3 my $code = $p{code};
82              
83 1 50       6 die q{'\@' or '=' are not allowed in locale codes}
84             if $code =~ /[\@=]/;
85              
86 1         3 $code =~ s/_/-/g;
87              
88 1         7 DateTime::Locale::Data::add_locale( $code, \%p );
89 1         9 return $LoadCache{$code} = DateTime::Locale::FromData->new( \%p );
90             }
91              
92             sub add_aliases {
93 5     5 0 6481 shift;
94              
95 5         9 %LoadCache = ();
96              
97 5 50       22 my $aliases = ref $_[0] ? $_[0] : {@_};
98              
99 5         8 for my $alias ( keys %{$aliases} ) {
  5         16  
100 5         21 my $code = $aliases->{$alias};
101              
102 5 100       20 die q{Can't alias an id to itself}
103             if $alias eq $code;
104              
105             # check for overwrite?
106              
107 4         14 my %seen = ( $alias => 1, $code => 1 );
108 4         7 my $copy = $code;
109 4         14 while ( $copy = $UserDefinedAlias{$copy} ) {
110             die
111             "Creating an alias from $alias to $code would create a loop.\n"
112 4 100       17 if $seen{$copy};
113              
114 3         8 $seen{$copy} = 1;
115             }
116              
117 3         10 $UserDefinedAlias{$alias} = $code;
118             }
119             }
120              
121             sub remove_alias {
122 1     1 0 3 shift;
123              
124 1         2 %LoadCache = ();
125              
126 1         2 my $alias = shift;
127              
128 1         5 return delete $UserDefinedAlias{$alias};
129             }
130              
131             # deprecated
132             sub ids {
133 0     0 0 0 shift->codes;
134             }
135              
136             ## no critic (Variables::ProhibitPackageVars)
137             sub codes {
138             wantarray
139 2 50   2 1 2090 ? keys %DateTime::Locale::Data::Codes
140             : [ keys %DateTime::Locale::Data::Codes ];
141             }
142              
143             sub names {
144             wantarray
145 1 50   1 1 277 ? keys %DateTime::Locale::Data::Names
146             : [ keys %DateTime::Locale::Data::Names ];
147             }
148              
149             sub native_names {
150             wantarray
151 0 0   0 1 0 ? keys %DateTime::Locale::Data::NativeNames
152             : [ keys %DateTime::Locale::Data::NativeNames ];
153             }
154              
155             # These are hard-coded for backwards comaptibility with the DateTime::Language
156             # code.
157             my %DateTimeLanguageAliases = (
158              
159             # 'Afar' => 'aa',
160             'Amharic' => 'am-ET',
161             'Austrian' => 'de-AT',
162             'Brazilian' => 'pt-BR',
163             'Czech' => 'cs-CZ',
164             'Danish' => 'da-DK',
165             'Dutch' => 'nl-NL',
166             'English' => 'en-US',
167             'French' => 'fr-FR',
168              
169             # 'Gedeo' => undef, # XXX
170             'German' => 'de-DE',
171             'Italian' => 'it-IT',
172             'Norwegian' => 'no-NO',
173             'Oromo' => 'om-ET', # Maybe om-KE or plain om ?
174             'Portugese' => 'pt-PT',
175              
176             # 'Sidama' => 'sid',
177             'Somali' => 'so-SO',
178             'Spanish' => 'es-ES',
179             'Swedish' => 'sv-SE',
180              
181             # 'Tigre' => 'tig',
182             'TigrinyaEthiopian' => 'ti-ET',
183             'TigrinyaEritrean' => 'ti-ER',
184             );
185              
186             my %POSIXAliases = (
187             C => 'en-US',
188             POSIX => 'en-US',
189             );
190              
191             {
192             my $validator = validation_for(
193             name => '_check_load_params',
194             name_is_optional => 1,
195             params => [
196             { type => t('NonEmptyStr') },
197             ],
198             );
199              
200             sub load {
201 1067     1067 1 4607387 my $class = shift;
202 1067         28761 my ($code) = $validator->(@_);
203              
204             # We used to use underscores in codes instead of dashes. We want to
205             # support both indefinitely.
206 1067         14361 $code =~ tr/_/-/;
207              
208             # Strip off charset for LC_* codes : en_GB.UTF-8 etc
209 1067         2255 $code =~ s/\..*$//;
210              
211 1067 100       4648 return $LoadCache{$code} if exists $LoadCache{$code};
212              
213 1057         2761 while ( exists $UserDefinedAlias{$code} ) {
214 3         8 $code = $UserDefinedAlias{$code};
215             }
216              
217             $code = $DateTimeLanguageAliases{$code}
218 1057 100       2772 if exists $DateTimeLanguageAliases{$code};
219 1057 100       2304 $code = $POSIXAliases{$code} if exists $POSIXAliases{$code};
220             $code = $DateTime::Locale::Data::ISO639Aliases{$code}
221 1057 100       3258 if exists $DateTime::Locale::Data::ISO639Aliases{$code};
222              
223 1057 100       3479 if ( exists $DateTime::Locale::Data::Codes{$code} ) {
224 1038         3006 return $class->_locale_object_for($code);
225             }
226              
227 19 100       84 if ( exists $DateTime::Locale::Data::Names{$code} ) {
228             return $class->_locale_object_for(
229 4         18 $DateTime::Locale::Data::Names{$code} );
230             }
231              
232 15 100       78 if ( exists $DateTime::Locale::Data::NativeNames{$code} ) {
233             return $class->_locale_object_for(
234 1         4 $DateTime::Locale::Data::NativeNames{$code} );
235             }
236              
237 14 100       45 if ( my $locale = $class->_registered_locale_for($code) ) {
238 7         23 return $locale;
239             }
240              
241 7 100       26 if ( my $guessed = $class->_guess_code($code) ) {
242 5         11 return $class->_locale_object_for($guessed);
243             }
244              
245 2         16 die "Invalid locale code or name: $code\n";
246             }
247             }
248              
249             sub _guess_code {
250 7     7   12 shift;
251 7         11 my $code = shift;
252              
253 7         55 my %codes = parse_locale_code($code);
254              
255 7         14 my @guesses;
256              
257 7 100       20 if ( $codes{script} ) {
258 1         4 my $guess = join q{-}, $codes{language}, $codes{script};
259              
260 1         2 push @guesses, $guess;
261              
262 1 50       5 $guess .= q{-} . $codes{territory} if defined $codes{territory};
263              
264             # version with script comes first
265 1         3 unshift @guesses, $guess;
266             }
267              
268 7 50       18 if ( $codes{variant} ) {
269             push @guesses, join q{-}, $codes{language}, $codes{territory},
270 0         0 $codes{variant};
271             }
272              
273 7 100       44 if ( $codes{territory} ) {
274 4         12 push @guesses, join q{-}, $codes{language}, $codes{territory};
275             }
276              
277 7         49 push @guesses, $codes{language};
278              
279 7         18 for my $code (@guesses) {
280             return $code
281             if exists $DateTime::Locale::Data::Codes{$code}
282 7 100 66     51 || exists $DateTime::Locale::Data::Names{$code};
283             }
284             }
285              
286             sub _locale_object_for {
287 1048     1048   1626 shift;
288 1048         1790 my $code = shift;
289              
290 1048 50       3074 my $data = DateTime::Locale::Data::locale_data($code)
291             or return;
292              
293             # We want to make a copy of the data just in case ...
294 1048         1974 return $LoadCache{$code} = DateTime::Locale::FromData->new( \%{$data} );
  1048         7224  
295             }
296              
297             sub _registered_locale_for {
298 14     14   27 my $class = shift;
299 14         28 my $code = shift;
300              
301             # Custom locale registered by user
302 14 100       41 if ( $Class{$code} ) {
303             return $LoadCache{$code}
304 2         10 = $class->_load_class_from_code( $code, $Class{$code} );
305             }
306              
307 12 100       40 if ( $DataForCode{$code} ) {
308 5         44 return $LoadCache{$code} = $class->_load_class_from_code($code);
309             }
310              
311 7 50       18 if ( $NameToCode{$code} ) {
312             return $LoadCache{$code}
313 0         0 = $class->_load_class_from_code( $NameToCode{$code} );
314             }
315              
316 7 50       25 if ( $NativeNameToCode{$code} ) {
317             return $LoadCache{$code}
318 0         0 = $class->_load_class_from_code( $NativeNameToCode{$code} );
319             }
320             }
321              
322             sub _load_class_from_code {
323 7     7   15 my $class = shift;
324 7         11 my $code = shift;
325 7         12 my $real_class = shift;
326              
327             # We want the first alias for which there is data, even if it has
328             # no corresponding .pm file. There may be multiple levels of
329             # alias to go through.
330 7         11 my $data_code = $code;
331 7   33     26 while ( exists $UserDefinedAlias{$data_code}
332             && !exists $DataForCode{$data_code} ) {
333              
334 0         0 $data_code = $UserDefinedAlias{$data_code};
335             }
336              
337 7         27 ( my $underscore_code = $data_code ) =~ s/-/_/g;
338 7   66     34 $real_class ||= "DateTime::Locale::$underscore_code";
339              
340 7 50       109 unless ( $real_class->can('new') ) {
341             ## no critic (BuiltinFunctions::ProhibitStringyEval, ErrorHandling::RequireCheckingReturnValueOfEval)
342 0         0 eval "require $real_class";
343 0 0       0 die $@ if $@;
344             ## use critic
345             }
346              
347             my $locale = $real_class->new(
348 7         17 %{ $DataForCode{$data_code} },
  7         43  
349             code => $code,
350             );
351              
352 7 100       42 if ( $locale->can('cldr_version') ) {
353 1         5 my $object_version = $locale->cldr_version;
354              
355 1 50       9 if ( $object_version ne $DateTime::Locale::Data::CLDRVersion ) {
356 1         16 warn
357             "Loaded $real_class, which is from an older version ($object_version)"
358             . ' of the CLDR database than this installation of'
359             . " DateTime::Locale ($DateTime::Locale::Data::CLDRVersion).\n";
360             }
361             }
362              
363 7         62 return $locale;
364             }
365             ## use critic
366              
367             1;
368              
369             # ABSTRACT: Localization support for DateTime.pm
370              
371             __END__
372              
373             =pod
374              
375             =encoding UTF-8
376              
377             =head1 NAME
378              
379             DateTime::Locale - Localization support for DateTime.pm
380              
381             =head1 VERSION
382              
383             version 1.39
384              
385             =head1 SYNOPSIS
386              
387             use DateTime::Locale;
388              
389             my $loc = DateTime::Locale->load('en-GB');
390              
391             print $loc->native_name, "\n", $loc->datetime_format_long, "\n";
392              
393             # but mostly just things like ...
394              
395             my $dt = DateTime->now( locale => 'fr' );
396             print "Aujourd'hui le mois est " . $dt->month_name, "\n";
397              
398             =head1 DESCRIPTION
399              
400             DateTime::Locale is primarily a factory for the various locale subclasses. It
401             also provides some functions for getting information on all the available
402             locales.
403              
404             If you want to know what methods are available for locale objects, then please
405             read the L<DateTime::Locale::FromData> documentation.
406              
407             =head1 USAGE
408              
409             This module provides the following class methods:
410              
411             =head2 DateTime::Locale->load( $locale_code | $locale_name )
412              
413             Returns the locale object for the specified locale code or name - see the
414             L<DateTime::Locale::Catalog> documentation for the list of available codes and
415             names. The name provided may be either the English or native name.
416              
417             If the requested locale is not found, a fallback search takes place to find a
418             suitable replacement.
419              
420             The fallback search order is:
421              
422             {language}-{script}-{territory}
423             {language}-{script}
424             {language}-{territory}-{variant}
425             {language}-{territory}
426             {language}
427              
428             Eg. For the locale code C<es-XX-UNKNOWN> the fallback search would be:
429              
430             es-XX-UNKNOWN # Fails - no such locale
431             es-XX # Fails - no such locale
432             es # Found - the es locale is returned as the
433             # closest match to the requested id
434              
435             Eg. For the locale code C<es-Latn-XX> the fallback search would be:
436              
437             es-Latn-XX # Fails - no such locale
438             es-Latn # Fails - no such locale
439             es-XX # Fails - no such locale
440             es # Found - the es locale is returned as the
441             # closest match to the requested id
442              
443             If no suitable replacement is found, then an exception is thrown.
444              
445             The loaded locale is cached, so that B<locale objects may be singletons>.
446             Calling C<< DateTime::Locale->register_from_data >>, C<<
447             DateTime::Locale->add_aliases >>, or C<< DateTime::Locale->remove_alias >>
448             clears the cache.
449              
450             =head2 DateTime::Locale->codes
451              
452             my @codes = DateTime::Locale->codes;
453             my $codes = DateTime::Locale->codes;
454              
455             Returns an unsorted list of the available locale codes, or an array reference
456             if called in a scalar context. This list does not include aliases.
457              
458             =head2 DateTime::Locale->names
459              
460             my @names = DateTime::Locale->names;
461             my $names = DateTime::Locale->names;
462              
463             Returns an unsorted list of the available locale names in English, or an array
464             reference if called in a scalar context.
465              
466             =head2 DateTime::Locale->native_names
467              
468             my @names = DateTime::Locale->native_names;
469             my $names = DateTime::Locale->native_names;
470              
471             Returns an unsorted list of the available locale names in their native
472             language, or an array reference if called in a scalar context. All native names
473             use UTF-8 as appropriate.
474              
475             =head2 DateTime::Locale->register_from_data( $locale_data )
476              
477             This method allows you to register a custom locale. The data for the locale is
478             specified as a hash (or hashref) where the keys match the method names given in
479             L<DateTime::Locale::FromData>.
480              
481             If you just want to make some small changes on top of an existing locale you
482             can get that locale's data by calling C<< $locale->locale_data >>.
483              
484             Here is an example of making a custom locale based off of C<en-US>:
485              
486             my $locale = DateTime::Locale->load('en-US');
487             my %data = $locale->locale_data;
488             $data{code} = 'en-US-CUSTOM';
489             $data{time_format_medium} = 'HH:mm:ss';
490              
491             DateTime::Locale->register_from_data(%data);
492              
493             # Prints 18:24:38
494             say DateTime->now( locale => 'en-US-CUSTOM' )->strftime('%X');
495              
496             # Prints 6:24:38 PM
497             say DateTime->now( locale => 'en-US' )->strftime('%X');
498              
499             The keys that should be present in the hash are the same as the accessor
500             methods provided by L<DateTime::Locale::FromData>, except for the following:
501              
502             =over 4
503              
504             =item The C<*_code> methods
505              
506             While you should provide a C<code> key, the other methods like C<language_code>
507             and C<script_code> are determined by parsing the code.
508              
509             =item All C<id> returning methods
510              
511             These are aliases for the corresponding C<*code> methods.
512              
513             =item C<prefers_24_hour_time>
514              
515             This is determined by looking at the short time format to see how it formats
516             hours,
517              
518             =item C<date_format_default> and C<time_format_default>
519              
520             These are the corresponding medium formats.
521              
522             =item C<datetime_format> and C<datetime_format_default>
523              
524             This is the same as the medium format.
525              
526             =item C<date_formats> and C<time_formats>
527              
528             These are calculated as needed.
529              
530             =item C<available_formats>
531              
532             This should be provided as a hashref where the keys are things like C<Gy> or
533             C<MMMEd> and the values are an actual format like C<"y G"> or C<"E, MMM d">.
534              
535             =item C<locale_data>
536              
537             This is everything you pass in.
538              
539             =back
540              
541             =head1 LOADING LOCALES IN A PRE-FORKING SYSTEM
542              
543             If you are running an application that does pre-forking (for example with
544             Starman), then you should try to load all the locales that you'll need in the
545             parent process. Locales are loaded on-demand, so loading them once in each
546             child will waste memory that could otherwise be shared.
547              
548             =head1 CLDR DATA BUGS
549              
550             Please be aware that all locale data has been generated from the CLDR (Common
551             Locale Data Repository) project locales data). The data is incomplete, and may
552             contain errors in some locales.
553              
554             When reporting errors in data, please check the primary data sources first,
555             then where necessary report errors directly to the primary source via the CLDR
556             bug report system. See L<http://unicode.org/cldr/filing_bug_reports.html> for
557             details.
558              
559             Once these errors have been confirmed, please forward the error report and
560             corrections to the DateTime mailing list, datetime@perl.org.
561              
562             =head1 AUTHOR EMERITUS
563              
564             Richard Evans wrote the first version of DateTime::Locale, including the tools
565             to extract the CLDR data.
566              
567             =head1 SEE ALSO
568              
569             datetime@perl.org mailing list
570              
571             =head1 SUPPORT
572              
573             Bugs may be submitted at L<https://github.com/houseabsolute/DateTime-Locale/issues>.
574              
575             There is a mailing list available for users of this distribution,
576             L<mailto:datetime@perl.org>.
577              
578             =head1 SOURCE
579              
580             The source code repository for DateTime-Locale can be found at L<https://github.com/houseabsolute/DateTime-Locale>.
581              
582             =head1 DONATIONS
583              
584             If you'd like to thank me for the work I've done on this module, please
585             consider making a "donation" to me via PayPal. I spend a lot of free time
586             creating free software, and would appreciate any support you'd care to offer.
587              
588             Please note that B<I am not suggesting that you must do this> in order for me
589             to continue working on this particular software. I will continue to do so,
590             inasmuch as I have in the past, for as long as it interests me.
591              
592             Similarly, a donation made in this way will probably not make me work on this
593             software much more, unless I get so many donations that I can consider working
594             on free software full time (let's all have a chuckle at that together).
595              
596             To donate, log into PayPal and send money to autarch@urth.org, or use the
597             button at L<https://houseabsolute.com/foss-donations/>.
598              
599             =head1 AUTHOR
600              
601             Dave Rolsky <autarch@urth.org>
602              
603             =head1 CONTRIBUTORS
604              
605             =for stopwords Alexander Pankoff James Raspass Karen Etheridge Mohammad S Anwar Ryley Breiddal Sergey Leschenko yasu47b
606              
607             =over 4
608              
609             =item *
610              
611             Alexander Pankoff <ccntrq@screenri.de>
612              
613             =item *
614              
615             James Raspass <jraspass@gmail.com>
616              
617             =item *
618              
619             Karen Etheridge <ether@cpan.org>
620              
621             =item *
622              
623             Mohammad S Anwar <mohammad.anwar@yahoo.com>
624              
625             =item *
626              
627             Ryley Breiddal <rbreiddal@presinet.com>
628              
629             =item *
630              
631             Sergey Leschenko <Sergey.Leschenko@portaone.com>
632              
633             =item *
634              
635             yasu47b <nakayamayasuhiro1986@gmail.com>
636              
637             =back
638              
639             =head1 COPYRIGHT AND LICENSE
640              
641             This software is copyright (c) 2003 - 2023 by Dave Rolsky.
642              
643             This is free software; you can redistribute it and/or modify it under
644             the same terms as the Perl 5 programming language system itself.
645              
646             The full text of the license can be found in the
647             F<LICENSE> file included with this distribution.
648              
649             =cut