NAME

DateTime::Format::Intl - A Web Intl.DateTimeFormat Class Implementation

SYNOPSIS

use DateTime;
use DateTime::Format::Intl;
my $dt = DateTime->now;
my $fmt = DateTime::Format::Intl->new(
    # You can use ja-JP (Unicode / web-style) or ja_JP (system-style), it does not matter.
    'ja_JP', {
        localeMatcher => 'best fit',
        # The only one supported. You can use 'gregory' or 'gregorian' indifferently
        calendar => 'gregorian',
        # see getNumberingSystems() in Locale::Intl for the supported number systems
        numberingSystem => 'latn',
        formatMatcher => 'best fit',
        dateStyle => 'long',
        timeStyle => 'long',
    },
) || die( DateTime::Format::Intl->error );
say $fmt->format( $dt );

my $fmt = DateTime::Format::Intl->new(
    # You can also use ja-JP (Unicode / web-style) or ja_JP (system-style), it does not matter.
    'ja_JP', {
        localeMatcher => 'best fit',
        # The only one supported
        calendar => 'gregorian',
        numberingSystem => 'latn',
        hour12 => 0,
        timeZone => 'Asia/Tokyo',
        weekday => 'long',
        era => 'short',
        year => 'numeric',
        month => '2-digit',
        day => '2-digit',
        dayPeriod => 'long',
        hour => '2-digit',
        minute => '2-digit',
        second => '2-digit',
        fractionalSecondDigits => 3,
        timeZoneName => 'long',
        formatMatcher => 'best fit',
    },
) || die( DateTime::Format::Intl->error );
say $fmt->format( $dt );

In basic use without specifying a locale, DateTime::Format::Intl uses the default locale and default options:

use DateTime;
my $date = DateTime->new(
    year    => 2012,
    month   => 11,
    day     => 20,
    hour    => 3,
    minute  => 0,
    second  => 0,
    # Default
    time_zone => 'UTC',
);
# toLocaleString without arguments depends on the implementation,
# the default locale, and the default time zone
say DateTime::Format::Intl->new->format( $date );
# "12/19/2012" if run with en-US locale (language) and time zone America/Los_Angeles (UTC-0800)

Using timeStyle and dateStyle:

Possible values are: full, long, medium and short

my $now = DateTime->new(
    year => 2024,
    month => 9,
    day => 13,
    hour => 14,
    minute => 12,
    second => 10,
    time_zone => 'Europe/Paris',
);
my $shortTime = DateTime::Format::Intl->new('en', {
    timeStyle => 'short',
});
say $shortTime->format( $now ); # "2:12 PM"

my $shortDate = DateTime::Format::Intl->new('en', {
    dateStyle => 'short',
});
say $shortDate->format( $now ); # "09/13/24"

my $mediumTime = DateTime::Format::Intl->new('en', {
    timeStyle => 'medium',
    dateStyle => 'short',
});
say $mediumTime->format( $now ); # "09/13/24, 2:12:10 PM"

my $shortDate = DateTime::Format::Intl->new('en', {
    dateStyle => 'medium',
});
say $shortDate->format( $now ); # "13 Sep 2024"

my $shortDate = DateTime::Format::Intl->new('en', {
    dateStyle => 'long',
});
say $shortDate->format( $now ); # "September 13, 2024"

my $shortDate = DateTime::Format::Intl->new('en', {
    dateStyle => 'long',
    timeStyle => 'long',
});
say $shortDate->format( $now ); # "September 13, 2024 at 2:12:10 PM GMT+1"

my $shortDate = DateTime::Format::Intl->new('en', {
    dateStyle => 'full',
});
say $shortDate->format( $now ); # "Friday, September 13, 2024"

my $shortDate = DateTime::Format::Intl->new('en', {
    dateStyle => 'full',
    timeStyle => 'full',
});
say $shortDate->format( $now ); # "Friday, September 13, 2024 at 2:12:10 PM Central European Standard Time"

Using dayPeriod:

Use the dayPeriod option to output a string for the times of day (in the morning, at night, noon, etc.). Note, that this only works when formatting for a 12 hour clock (hourCycle => 'h12' or hourCycle => 'h11') and that for many locales the strings are the same irrespective of the value passed for the dayPeriod.

my $date = DateTime->new(
    year    => 2012,
    month   => 11,
    day     => 17,
    hour    => 4,
    minute  => 0,
    second  => 42,
    # Default
    time_zone => 'UTC',
);

say DateTime::Format::Intl->new( 'en-GB', {
    hour        => 'numeric',
    hourCycle   => 'h12',
    dayPeriod   => 'short',
    # or 'time_zone' is ok too
    timeZone    => 'UTC',
})->format( $date );
# "4 at night" (same formatting in en-GB for all dayPeriod values)

say DateTime::Format::Intl->new( 'fr', {
    hour        => 'numeric',
    hourCycle   => 'h12',
    dayPeriod   => 'narrow',
    # or 'time_zone' is ok too
    timeZone    => 'UTC',
})->format( $date );
# "4 mat."  (same output in French for both narrow/short dayPeriod)

say DateTime::Format::Intl->new( 'fr', {
    hour        => 'numeric',
    hourCycle   => 'h12',
    dayPeriod   => 'long',
    # or 'time_zone' is ok too
    timeZone    => 'UTC',
})->format( $date );
# "4 du matin"

Using timeZoneName:

Use the timeZoneName option to output a string for the timezone (GMT, Pacific Time, etc.).

my $date = DateTime->new(
    year    => 2021,
    month   => 11,
    day     => 17,
    hour    => 3,
    minute  => 0,
    second  => 42,
    # Default
    time_zone => 'UTC',
);
my $timezoneNames = [qw(
    short
    long
    shortOffset
    longOffset
    shortGeneric
    longGeneric
)];

foreach my $zoneName ( @$timezoneNames )
{
    # Do something with currentValue
    my $formatter = DateTime::Format::Intl->new( 'en-US', {
        timeZone        => 'America/Los_Angeles',
        timeZoneName    => $zoneName,
    });
    say "${zoneName}: ", $formatter->format( $date);
}

# Yields the following:
# short: 12/16/2021, PST
# long: 12/16/2021, Pacific Standard Time
# shortOffset: 12/16/2021, GMT-8
# longOffset: 12/16/2021, GMT-08:00
# shortGeneric: 12/16/2021, PT
# longGeneric: 12/16/2021, Pacific Time

# Enabling fatal exceptions
use v5.34;
use experimental 'try';
no warnings 'experimental';
try
{
    my $fmt = DateTime::Format::Intl->new( 'x', fatal => 1 );
    # More code
}
catch( $e )
{
    say "Oops: ", $e->message;
}

Or, you could set the global variable $FATAL_EXCEPTIONS instead:

use v5.34;
use experimental 'try';
no warnings 'experimental';
local $DateTime::Format::Intl::FATAL_EXCEPTIONS = 1;
try
{
    my $fmt = DateTime::Format::Intl->new( 'x' );
    # More code
}
catch( $e )
{
    say "Oops: ", $e->message;
}

VERSION

v0.1.0

DESCRIPTION

This module provides the equivalent of the JavaScript implementation of Intl.DateTimeFormat

It relies on DateTime::Format::Unicode, DateTime::Locale::FromCLDR, Locale::Unicode::Data, which provides access to all the Unicode CLDR (Common Locale Data Repository), and Locale::Intl to achieve similar results. It requires perl v5.10.1 minimum to run.

It is very elaborate and the algorithm provides the same result you would get with a web browser. The algorithm itself is quite complex and took me several months to implement, given all the dependencies with the modules aforementioned it relies on, that I also had to build to make the whole thing work.

I hope they will benefit you as they benefit me.

Because, just like its JavaScript equivalent, DateTime::Format::Intl does quite a bit of look-ups and sensible guessing upon object instantiation, you want to create an object for a specific format, cache it and re-use it rather than creating a new one for each date formatting.

DateTime::Format::Intl uses a set of culturally sensible default values derived directly from the web browsers own default. Upon object instantiation, it uses a culturally sensitive scoring to find the best matching format pattern available in the Unicode CLDR (Common Locale Data Repository) data for the options provided. It appends any missing components, if any. Finally, it adjusts the best pattern retained to match perfectly the options of the user.

CONSTRUCTOR

new

This takes a locale (a.k.a. language code compliant with ISO 15924 as defined by IETF) and an hash or hash reference of options and will return a new DateTime::Format::Intl object, or upon failure undef in scalar context and an empty list in list context.

Each option can also be accessed or changed using their corresponding method of the same name.

See the CLDR (Unicode Common Locale Data Repository) page for more on the format patterns used.

Supported options are:

Locale options

Date-time component options

Style shortcuts

Note: dateStyle and timeStyle can be used with each other, but not with other date-time component options (e.g. weekday, hour, month, etc.).

METHODS

format

format_range

Same as formatRange

format_range_to_parts

Same as formatRangeToParts

format_to_parts

Same as formatToParts

formatRange

my $d1 = DateTime->new(
    year    => 2024,
    month   => 5,
    day     => 10,
    hour    => 13,
    minute  => 0,
    second  => 0,
);
my $d2 = DateTime->new(
    year    => 2024,
    month   => 5,
    day     => 11,
    hour    => 14,
    minute  => 0,
    second  => 0,
);
my $fmt = DateTime::Format::Intl->new( 'fr-FR' );
say $fmt->formatRange( $d1 => $d2 ); # 10/05/2024 - 11/05/2024

my $fmt2 = DateTime::Format::Intl->new( 'ja-JP' );
say $fmt2->formatRange( $d1 => $d2 ); # 2024/05/10~2024/05/11

my $fmt3 = DateTime::Format::Intl->new( 'fr-FR', {
    weekday => 'long',
    year    => 'numeric',
    month   => 'long',
    day     => 'numeric',
});
say $fmt3->formatRange( $d1 => $d2 ); # vendredi 10 mai 2024 - samedi 11 mai 2024

This formatRange() method takes 2 DateTime objects, and formats the range between 2 dates and returns a string.

The format used is the most concise way based on the locales and options provided when instantiating the new DateTime::Format::Intl object. When no option were provided upon object instantiation, it default to a short version of the date format using date_format_short), which, in turn, gets interpreted in various formats depending on the locale chosen. In British English, this would be 10/05/2024 for May 10th, 2024.

formatRangeToParts

my $d1 = DateTime->new(
    year    => 2024,
    month   => 5,
    day     => 10,
    hour    => 13,
    minute  => 0,
    second  => 0,
);
my $d2 = DateTime->new(
    year    => 2024,
    month   => 5,
    day     => 11,
    hour    => 14,
    minute  => 0,
    second  => 0,
);
my $fmt = DateTime::Format::Intl->new( 'fr-FR', {
    weekday => 'long',
    year    => 'numeric',
    month   => 'long',
    day     => 'numeric',
});
say $fmt->formatRange( $d1, $d2 ); # mercredi 10 janvier à 19:00 – jeudi 11 janvier à 20:00
my $ref = $fmt->formatRangeToParts( $d1, $d2 );

This would return an array containing the following hash references:

{ type => 'weekday', value => 'mercredi',   source => 'startRange' },
{ type => 'literal', value => ' ',          source => 'startRange' },
{ type => 'day',     value => '10',         source => 'startRange' },
{ type => 'literal', value => ' ',          source => 'startRange' },
{ type => 'month',   value => 'janvier',    source => 'startRange' },
{ type => 'literal', value => ' à ',        source => 'startRange' },
{ type => 'hour',    value => '19',         source => 'startRange' },
{ type => 'literal', value => ':',          source => 'startRange' },
{ type => 'minute',  value => '00',         source => 'startRange' },
{ type => 'literal', value => ' – ',        source => 'shared' },
{ type => 'weekday', value => 'jeudi',      source => 'endRange' },
{ type => 'literal', value => ' ',          source => 'endRange' },
{ type => 'day',     value => '11',         source => 'endRange' },
{ type => 'literal', value => ' ',          source => 'endRange' },
{ type => 'month',   value => 'janvier',    source => 'endRange' },
{ type => 'literal', value => ' à ',        source => 'endRange' },
{ type => 'hour',    value => '20',         source => 'endRange' },
{ type => 'literal', value => ':',          source => 'endRange' },
{ type => 'minute',  value => '00',         source => 'endRange' }

The formatRangeToParts() method returns an array of locale-specific tokens representing each part of the formatted date range produced by this DateTime::Format::Intl object. It is useful for custom formatting of date strings.

formatToParts

my $d = DateTime->new(
    year    => 2024,
    month   => 5,
    day     => 10,
    hour    => 13,
    minute  => 0,
    second  => 0,
);
my $fmt = DateTime::Format::Intl->new( 'fr-FR', {
    weekday => 'long',
    year    => 'numeric',
    month   => 'long',
    day     => 'numeric',
});
say $fmt->format( $d ); # mercredi 10 janvier à 19:00
my $ref = $fmt->formatToParts( $d );

This would return an array containing the following hash references:

{ type => 'weekday', value => 'mercredi' },
{ type => 'literal', value => ' ' },
{ type => 'day',     value => '10' },
{ type => 'literal', value => ' ' },
{ type => 'month',   value => 'janvier' },
{ type => 'literal', value => ' à ' },
{ type => 'hour',    value => '19' },
{ type => 'literal', value => ':' },
{ type => 'minute',  value => '00' }

The formatToParts() method takes an optional DateTime object, and returns an array of locale-specific tokens representing each part of the formatted date produced by this DateTime::Format::Intl object. It is useful for custom formatting of date strings.

If no DateTime object is provided, it will default to the current date and time.

The properties of the hash references returned are as follows:

resolvedOptions

The resolvedOptions() method returns an hash reference with the following properties reflecting the locale and date and time formatting options computed during the object instantiation.

OTHER NON-CORE METHODS

error

Sets or gets an exception object

When called with parameters, this will instantiate a new DateTime::Format::Intl::Exception object, passing it all the parameters received.

When called in accessor mode, this will return the latest exception object set, if any.

fatal

$fmt->fatal(1); # Enable fatal exceptions
$fmt->fatal(0); # Disable fatal exceptions
my $bool = $fmt->fatal;

Sets or get the boolean value, whether to die upon exception, or not. If set to true, then instead of setting an exception object, this module will die with an exception object. You can catch the exception object then after using try. For example:

use v.5.34; # to be able to use try-catch blocks in perl
use experimental 'try';
no warnings 'experimental';
try
{
    my $fmt = DateTime::Format::Intl->new( 'x', fatal => 1 );
}
catch( $e )
{
    say "Error occurred: ", $e->message;
    # Error occurred: Invalid locale value "x" provided.
}

greatest_diff

my $fmt = DateTime::Format::Intl->new( 'fr-FR' );
say $fmt->formatRange( $d1 => $d2 ); # 10/05/2024 - 11/05/2024
# Found that day ('d') is the greatest difference between the two datetimes
my $component = $fmt->greatest_diff; # d

Read-only method.

Returns a string representing the component that is the greatest difference between two datetimes.

This value can be retrieved after formatRange or formatRangeToParts has been called, otherwise, it would merely return undef

This is a non-standard method, not part of the original Intl.DateTimeFormat JavaScript API.

See also "interval_greatest_diff" in DateTime::Locale::FromCLDR and the Unicode LDML specifications

interval_pattern

my $fmt = DateTime::Format::Intl->new( 'fr-FR' );
say $fmt->formatRange( $d1 => $d2 ); # 10/05/2024 - 11/05/2024
my $pattern = $fmt->interval_pattern;

Read-only method.

Returns a string representing the format pattern resulting from calling formatRange or formatRangeToParts. This format pattern, which is most likely based on interval format patterns available in the Unicode CLDR data, may have been adjusted to match the required options.

This is a non-standard method, not part of the original Intl.DateTimeFormat JavaScript API.

interval_skeleton

my $fmt = DateTime::Format::Intl->new( 'fr-FR' );
say $fmt->formatRange( $d1 => $d2 ); # 10/05/2024 - 11/05/2024
my $skeleton = $fmt->interval_skeleton;

Read-only method.

Returns a string representing the format skeleton resulting from calling formatRange or formatRangeToParts. This format skeleton, as called in the Unicode LDML specifications, is like an ID representing the underlying format pattern.

This is a non-standard method, not part of the original Intl.DateTimeFormat JavaScript API.

pattern

my $fmt = DateTime::Format::Intl->new( 'en', { weekday => 'short' } ) ||
    die( DateTime::Format::Intl->error );
my $resolved_pattern = $fmt->pattern;

Read-only method.

Returns a string representing the pattern resolved from the lookup based on the locale provided and options specified.

This is a non-standard method, not part of the original Intl.DateTimeFormat JavaScript API.

skeleton

my $fmt = DateTime::Format::Intl->new( 'en', { weekday => 'short' } ) ||
    die( DateTime::Format::Intl->error );
my $resolved_skeleton = $fmt->skeleton;

Read-only method.

Returns a string representing the skeleton resolved from the lookup based on the locale provided and options specified. This returns a value only if the neither of the constructor options dateStyle or timeStyle have been provided. Otherwise, it would be undef

This is a non-standard method, not part of the original Intl.DateTimeFormat JavaScript API.

CLASS FUNCTIONS

supportedLocalesOf

my $array = DateTime::Format::Intl->supportedLocalesOf( $locales, $options1 );
# Try 3 locales by order of priority
my $array = DateTime::Format::Intl->supportedLocalesOf( ['ja-t-de-t0-und-x0-medical', 'he-IL-u-ca-hebrew-tz-jeruslm', 'en-GB'], $options1 );

The supportedLocalesOf() class function returns an array containing those of the provided locales that are supported in DateTime::Locale::FromCLDR without having to fall back to the runtime's default locale.

It takes 2 arguments: locales to look up, and an hash or hash reference of options

EXCEPTIONS

A RangeError exception is thrown if locales or options contain invalid values.

If an error occurs, any given method will set the error object and return undef in scalar context, or an empty list in list context.

See Mozilla documentation for more information.

AUTHOR

Jacques Deguest <jack@deguest.jp>

SEE ALSO

Locale::Unicode, Locale::Intl, Locale::Unicode::Data, DateTime::Locale::FromCLDR, DateTime::Format::Unicode, DateTime

Mozilla documentation

CLDR repository for dates and time

ICU documentation

CLDR website

COPYRIGHT & LICENSE

Copyright(c) 2024 DEGUEST Pte. Ltd.

All rights reserved

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.