DateTime-Format-Unicode

 view release on metacpan or  search on metacpan

lib/DateTime/Format/Unicode.pm  view on Meta::CPAN

sub time_zone
{
    my $self = shift( @_ );
    if( @_ )
    {
        my $tz = shift( @_ );
        local $@;
        unless( Scalar::Util::blessed( $tz ) &&
                $tz->isa( 'DateTime::TimeZone' ) )
        {
            # try-catch
            eval
            {
                require DateTime::TimeZone;
            } || return( $self->error( "Unable to load the module DateTime::TimeZone: $@" ) );

            # try-catch
            $tz = eval
            {
                DateTime::TimeZone->new( name => "${tz}" );
            } || return( $self->error( "Unable to instantiate a new DateTime::TimeZone object from '${tz}': ", ( $@ || 'unknown error' ) ) );
        }
        $self->{time_zone} = $tz;
    }
    return( $self->{time_zone} );
}

# NOTE: pattern a
# AM/PM
sub _format_am_pm
{
    my( $self, $token, $len, $dt ) = @_;
    my $unicode = $self->{_unicode} || die( "DateTime::Locale::FromCLDR object is gone!" );
    # "a..aaa" (Abbreviated)
    # Example: am. [e.g. 12 am.]
    if( $len >= 1 && $len <= 3 )
    {
        return( $unicode->am_pm_format_abbreviated->[ $dt->hour < 12 ? 0 : 1 ] );
    }
    # "aaaa" (Wide)
    # Example: am. [e.g. 12 am.]
    elsif( $len == 4 )
    {
        return( $unicode->am_pm_format_wide->[ $dt->hour < 12 ? 0 : 1 ] );
    }
    # "aaaaa" (Narrow)
    # Example: a [e.g. 12a]
    elsif( $len == 5 )
    {
        return( $unicode->am_pm_format_narrow->[ $dt->hour < 12 ? 0 : 1 ] );
    }
    else
    {
        warn( "Unknown length '${len}' to format am/pm" ) if( warnings::enabled() );
        return( $token x $len );
    }
}

# NOTE: pattern U
# Missing in DateTime
# "If the calendar does not provide cyclic year name data, or if the year value to be formatted is out of the range of years for which cyclic name data is provided, then numeric formatting is used (behaves like 'y')."
sub _format_cyclic_year_name
{
    my( $self, $token, $len, $dt ) = @_;
    my $unicode = $self->{_unicode} || die( "DateTime::Locale::FromCLDR object is gone!" );
    my $year = $dt->year;
    # Abbreviated
    if( $len >= 1 && $len <= 3 )
    {
        my $era = $unicode->era_abbreviated->[ $year < 0 ? 0 : 1 ];
        $era = $year if( !length( $era // '' ) );
        return( $era );
    }
    # Wide
    elsif( $len == 4 )
    {
        my $era = $unicode->era_wide->[ $year < 0 ? 0 : 1 ];
        $era = $year if( !length( $era // '' ) );
        return( $era );
    }
    # Narrow
    elsif( $len == 5 )
    {
        my $era = $unicode->era_narrow->[ $year < 0 ? 0 : 1 ];
        $era = $year if( !length( $era // '' ) );
        return( $era );
    }
    else
    {
        warn( "Unknown length '${len}' to format era name" ) if( warnings::enabled() );
        return( $token x $len );
    }
}

# NOTE: pattern g
sub _format_day_julian
{
    my( $self, $token, $len, $dt ) = @_;
    if( $len >= 1 )
    {
        my $rv = sprintf( '%0*d', $len, $dt->mjd );
        $rv = $self->_localise_digits( $rv ) if( $self->{_number_system} ne 'latn' );
        return( $rv );
    }
    else
    {
        warn( "Unknown length '${len}' to format julian day" ) if( warnings::enabled() );
        return( $token x $len );
    }
}

# NOTE: pattern d
sub _format_day_of_month
{
    my( $self, $token, $len, $dt ) = @_;
    if( $len >= 1 && $len <= 2 )
    {
        my $rv = sprintf( '%0*d', $len, $dt->day_of_month );
        $rv = $self->_localise_digits( $rv ) if( $self->{_number_system} ne 'latn' );
        return( $rv );
    }

lib/DateTime/Format/Unicode.pm  view on Meta::CPAN

    ) || die( DateTime::Format::Unicode->error );

or, maybe, just:

    my $fmt = DateTime::Format::Unicode->new;

which, will default to C<locale> C<en> with date medium-size format pattern C<MMM d, y>

If you specify a C<locale> that uses a different number system than C<latn> (which is 0 to 9), then C<DateTime::Format::Unicode> will honour it. For example:

    my $fmt = DateTime::Format::Unicode->new(
        locale => 'ar-SA',
        pattern => 'd/M/y',
    ) || die( DateTime::Format::Unicode->error );
    say $fmt->format; # ١٠/٩/٢٠٢٤

You can also override the C<locale>'s default number system, by another one, as long as it is supported by that C<locale>. For example:

    my $fmt = DateTime::Format::Unicode->new(
        locale => 'ar-SA-u-nu-latn',
        pattern => 'd/M/y',
    ) || die( DateTime::Format::Unicode->error );
    say $fmt->format; # ١٠/٩/٢٠٢٤

=head1 VERSION

    v0.3.0

=head1 DESCRIPTION

This is a Unicode L<CLDR|https://cldr.unicode.org/> (Common Locale Data Repository) formatter for L<DateTime>

It differs from the default formatter used in L<DateTime> with its method L<format_cldr|DateTime/format_cldr> in several aspects:

=over 4

=item 1. It uses L<DateTime::Locale::FromCLDR>

A much more comprehensive and accurate API to dynamically access the Unicode C<CLDR> data whereas the module L<DateTime> relies on, L<DateTime::Locale>, which uses static data from over 1,000 pre-generated modules.

=item 2. It allows for any C<locale>

Since, it uses dynamic data, you can use any C<locale>, from the simple C<en> to more complex C<es-001-valencia>, or even C<ja-t-de-t0-und-x0-medical>

=item 3. It allows formatting of datetime intervals

Datetime intervals are very important, and unfortunately unsupported by L<DateTime> as of July 2024.

=item 4. It supports more pattern tokens

L<DateTime> L<format_cldr|DateTime/format_cldr> does not support all of the L<CLDR pattern tokens|https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns>, but L<DateTime::Format::Unicode> does.

Known pattern tokens unsupported by L<DateTime> are:

=over 8

=item * C<b>

Period of the day, such as C<am>, C<pm>, C<noon>, C<midnight>

See L<Locale::Unicode::Data/calendar_term> and its corollary L<Locale::Unicode::Data/day_period>

=item * C<B>

Flexible day periods, such as C<at night>

See L<Locale::Unicode::Data/calendar_term> and its corollary L<Locale::Unicode::Data/day_period>

=item * C<O>

Zone, such as C<O> to get the short localized GMT format C<GMT-8>, or C<OOOO> to get the long localized GMT format C<GMT-08:00>

=item * C<r>

Related Gregorian year (numeric).

The documentation states that "For the Gregorian calendar, the ‘r’ year is the same as the ‘u’ year."

=item * C<U>

Cyclic year name. However, since this is for non gregorian calendars, like Chinese or Hindu calendars, and since L<DateTime> only supports gregorian calendar, we do not support it either.

=item * C<x>

Timezone, such as C<x> would be C<-08>, C<xx> C<-0800> or C<+0800>, C<xxx> would be C<-08:00> or C<+08:00>, C<xxxx> would be C<-0800> or C<+0000> and C<xxxxx> would be C<-08:00>, or C<-07:52:58> or C<+00:00>

=item * C<X>

Timezone, such as C<X> (C<-08> or C<+0530> or C<Z>), C<XX> (C<-0800> or C<Z>), C<XXX> (C<-08:00>), C<XXXX> (C<-0800> or C<-075258> or C<Z>), C<XXXXX> (C<-08:00> or C<-07:52:58> or C<Z>)

=back

=back

L<DateTime::Format::Unicode> only formats C<CLDR> datetime patterns, and does not parse them back into a L<DateTime> object. If you want to achieve that, there is already the module L<DateTime::Format::CLDR> that does this. L<DateTime::Format::CLDR> ...

=head1 CONSTRUCTOR

=head2 new

This takes some hash or hash reference of options, instantiates a new L<DateTime::Format::Unicode> object, and returns it.

Supported options are as follows. Each option can be later accessed or modified by their associated method.

=over 4

=item * C<locale>

A L<locale|Locale::Unicode>, which may be very simple like C<en> or much more complex like C<ja-t-de-t0-und-x0-medical> or maybe C<es-039-valencia> (valencian variant of Spanish as spoken in South Europe)

If not provided, this will default to C<en>

=item * C<on_error>

Specifies what to do upon error. Possible values are: C<undef> (default behaviour), C<fatal> (will die), or a C<CODE> reference that will be called with the L<exception object|DateTime::Format::Unicode::Exception> as its sole argument, before C<undef...

=item * C<pattern>

A C<CLDR> pattern. If none is provided, this will default to the medium-size date pattern for the given C<locale>. For example, as per the C<CLDR>, for English, this would be C<MMM d, y> whereas for the C<locale> C<ja>, this would be C<y/MM/dd>

=item * C<time_zone>

Set the timezone by providing either a L<DateTime::TimeZone> object, or a string representing a timezone.

It defaults to the special L<DateTime> timezone L<floating|DateTime::TimeZone::Floating>

=back

=head1 METHODS

=head2 error

Used as a mutator, this sets an L<exception object|DateTime::Format::Unicode::Exception> and returns an C<DateTime::Format::Unicode::NullObject> in object context (such as when chaining), or C<undef> in scalar context, or an empty list in list contex...

The C<DateTime::Format::Unicode::NullObject> class prevents the perl error of C<Can't call method "%s" on an undefined value> (see L<perldiag>). Upon the last method chained, C<undef> is returned in scalar context or an empty list in list context.

=head2 format_datetime

    my $fmt = DateTime::Format::Unicode->new(
        locale => 'en',
        pattern => "Hello the time is H:m:s",



( run in 2.346 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )