Calendar-Indonesia-Holiday

 view release on metacpan or  search on metacpan

lib/Calendar/Indonesia/Holiday.pm  view on Meta::CPAN

        day        => 17, month =>  8,
        ind_name   => "Proklamasi",
        eng_name   => "Declaration Of Independence",
        tags       => [qw/national/],
        fixed_date => 1,
    },
    my $christmas = sub {
        return {
            day        => 25, month => 12,
            ind_name   => ($year && ($year >= 2025) ? "Kelahiran Yesus Kristus" : "Natal"),
            eng_name   => "Christmas",
            tags       => [qw/international religious religion=christianity/],
            fixed_date => 1,
        };
    },
    my $labord = {
        day         => 1, month => 5,
        year_start  => 2014,
        ind_name    => "Hari Buruh",
        eng_name    => "Labor Day",
        tags        => [qw/international/],
        decree_date => "2013-04-29",
        decree_note => "Labor day becomes national holiday since 2014, ".
            "decreed by president",
        fixed_date  => 1,
    },
    my $pancasilad = {
        day         => 1, month => 6,
        year_start  => 2017,
        ind_name    => "Hari Lahir Pancasila",
        eng_name    => "Pancasila Day",
        tags        => [qw/national/],
        decree_date => "2016-06-01",
        decree_note => "Pancasila day becomes national holiday since 2017, ".
            "decreed by president (Keppres 24/2016)",
        # ref: http://www.kemendagri.go.id/media/documents/2016/08/03/k/e/keppres_no.24_th_2016.pdf
        fixed_date  => 1,
    },
);

sub _add_original_date {
    my ($r, $opts) = @_;
    if ($opts->{original_date}) {
        $r->{ind_name} .= " (diperingati $opts->{original_date})";
        $r->{eng_name} .= " (commemorated on $opts->{original_date})";
    }
}

sub _h_chnewyear {
    my ($r, $opts) = @_;
    $opts //= {};
    $r->{ind_name}    = "Tahun Baru Imlek".
        ($opts->{hyear} ? " $opts->{hyear}" . ($year && $year >= 2024 ? " Kongzili" : ""):"");
    $r->{eng_name}    = "Chinese New Year".
        ($opts->{hyear} ? " $opts->{hyear}":"");
    _add_original_date($r, $opts);
    $r->{ind_aliases} = [];
    $r->{eng_aliases} = [];
    $r->{is_holiday}  = 1;
    $r->{year_start}  = 2003; # decreed in 2002 by megawati soekarnoputri
    $r->{tags}        = [qw/international calendar=lunar/];
    ($r);
}

sub _h_mawlid {
    my ($r, $opts) = @_;
    $r->{ind_name}    = "Maulid Nabi Muhammad";
    $r->{eng_name}    = "Mawlid";
    _add_original_date($r, $opts);
    $r->{ind_aliases} = [qw/Maulud/];
    $r->{eng_aliases} = ["Mawlid An-Nabi"];
    $r->{is_holiday}  = 1;
    $r->{tags}        = [qw/religious religion=islam calendar=lunar/];
    ($r);
}

sub _h_nyepi {
    my ($r, $opts) = @_;
    $r->{ind_name}    = "Nyepi".
        ($opts->{hyear} ? " $opts->{hyear}":"");
    $r->{eng_name}    = "Nyepi".
        ($opts->{hyear} ? " $opts->{hyear}":"");
    _add_original_date($r, $opts);
    $r->{ind_aliases} = ["Tahun Baru Saka"];
    $r->{eng_aliases} = ["Bali New Year", "Bali Day Of Silence"];
    $r->{is_holiday}  = 1;
    $r->{tags}        = [qw/religious religion=hinduism calendar=saka/];
    ($r);
}

sub _h_goodfri {
    my ($r, $opts) = @_;
    $r->{ind_name}    = "Jum'at Agung";
    $r->{eng_name}    = "Good Friday";
    _add_original_date($r, $opts);
    $r->{ind_aliases} = ["Wafat Isa Al-Masih"];
    $r->{eng_aliases} = [];
    $r->{is_holiday}  = 1;
    $r->{tags}        = [qw/religious religion=christianity/];
    ($r);
}

# since 2024
sub _h_easter {
    my ($r, $opts) = @_;
    $opts //= {};
    $r->{ind_name}    = $year && $year >= 2025 ?
        "Kebangkitan Yesus Kristus (Paskah)" : "Hari Paskah";
    $r->{eng_name}    = "Easter";
    _add_original_date($r, $opts);
    $r->{ind_aliases} = [];
    $r->{eng_aliases} = [];
    $r->{is_holiday}  = 1;
    $r->{tags}        = [qw/religious religion=christianity/];
    ($r);
}

sub _h_vesakha {
    my ($r, $opts) = @_;
    $r->{ind_name}    = "Waisyak".
        ($opts->{hyear} ? " $opts->{hyear}":"");
    $r->{eng_name}    = "Vesakha".
        ($opts->{hyear} ? " $opts->{hyear}":"");
    _add_original_date($r, $opts);
    $r->{ind_aliases} = [];
    $r->{eng_aliases} = ["Vesak"];
    $r->{is_holiday}  = 1;
    $r->{tags}        = [qw/religious religion=buddhism/];
    ($r);
}

sub _h_ascension {
    my ($r, $opts) = @_;
    $opts //= {};
    $r->{ind_name}    = $year && $year >= 2024 ?
        "Kenaikan Yesus Kristus" : "Kenaikan Isa Al-Masih";
    $r->{eng_name}    = "Ascension Day";
    _add_original_date($r, $opts);
    $r->{ind_aliases} = [];
    $r->{eng_aliases} = [];
    $r->{is_holiday}  = 1;
    $r->{tags}        = [qw/religious religion=christianity/];
    ($r);
}

sub _h_isramiraj {
    my ($r, $opts) = @_;
    $r->{ind_name}    = "Isra Miraj";
    $r->{eng_name}    = "Isra And Miraj";
    _add_original_date($r, $opts);
    $r->{ind_aliases} = [];
    $r->{eng_aliases} = [];
    $r->{is_holiday}  = 1;
    $r->{tags}        = [qw/religious religion=islam calendar=lunar/];
    ($r);
}

sub _h_eidulf {
    my ($r, $opts) = @_;
    $opts //= {};
    my $ind_name0     = "Idul Fitri".
        ($opts->{hyear} ? " $opts->{hyear}H":"");
    my $eng_name0     = "Eid Ul-Fitr".
        ($opts->{hyear} ? " $opts->{hyear}H":"");
    $r->{ind_name}    = $ind_name0.($opts->{day} ? " (Hari $opts->{day})":"");
    $r->{eng_name}    = $eng_name0.($opts->{day} ? " (Day $opts->{day})":"");
    _add_original_date($r, $opts);
    $r->{ind_aliases} = ["Lebaran"];
    $r->{eng_aliases} = [];
    $r->{is_holiday}  = 1;
    $r->{tags}        = [qw/religious religion=islam calendar=lunar/];
    ($r);
}

sub _h_eidula {
    my ($r, $opts) = @_;
    $r->{ind_name}    = "Idul Adha";
    $r->{eng_name}    = "Eid Al-Adha";
    _add_original_date($r, $opts);
    $r->{ind_aliases} = ["Idul Kurban"];
    $r->{eng_aliases} = [];
    $r->{is_holiday}  = 1;
    $r->{tags}        = [qw/religious religion=islam calendar=lunar/];
    ($r);
}

sub _h_hijra {
    my ($r, $opts) = @_;
    $opts //= {};
    $r->{ind_name}    = "Tahun Baru Hijriyah".
        ($opts->{hyear} ? " $opts->{hyear}H":"");
    $r->{eng_name}    = "Hijra".
        ($opts->{hyear} ? " $opts->{hyear}H":"");
    _add_original_date($r, $opts);
    $r->{ind_aliases} = ["1 Muharam"];
    $r->{eng_aliases} = [];
    $r->{is_holiday}  = 1;
    $r->{tags}        = [qw/calendar=lunar/];
    ($r);
}

sub _h_lelection {
    my ($r, $opts) = @_;
    $r->{ind_name}    = "Pemilu Legislatif (Pileg)";
    $r->{eng_name}    = "Legislative Election";
    $r->{is_holiday}  = 1;
    $r->{tags}        = [qw/political/];

    for (qw(decree_date decree_note)) {
        $r->{$_} = $opts->{$_} if defined $opts->{$_};
    }
    ($r);
}

sub _h_pelection {
    my ($r, $opts) = @_;
    $r->{ind_name}    = "Pemilu Presiden (Pilpres)";
    $r->{eng_name}    = "Presidential Election";
    $r->{is_holiday}  = 1;
    $r->{tags}        = [qw/political/];

    for (qw(decree_date decree_note)) {
        $r->{$_} = $opts->{$_} if defined $opts->{$_};
    }
    ($r);
}

sub _h_jrelection {
    my ($r, $opts) = @_;
    $r->{ind_name}    = "Pilkada Serentak";
    $r->{eng_name}    = "Joint Regional Election";
    $r->{is_holiday}  = 1;
    $r->{tags}        = [qw/political/];

    for (qw(decree_date decree_note)) {
        $r->{$_} = $opts->{$_} if defined $opts->{$_};
    }
    ($r);
}

sub _jointlv {
    my ($r, $opts) = @_;
    $opts //= {};
    my $h = $opts->{holiday};
    $r->{ind_name}        = "Cuti Bersama".
        ($h ? " (".($h->{ind_name0} // $h->{ind_name}).")": "");
    $r->{eng_name}        = "Joint Leave".
        ($h ? " (".($h->{eng_name0} // $h->{eng_name}).")": "");
    $r->{ind_aliases}     = [];
    $r->{eng_aliases}     = [];
    $r->{is_joint_leave}  = 1;
    $r->{tags}            = [];
    ($r);
}

# can operate on a single holiday or multiple ones
sub _make_tentative {
    for my $arg (@_) {

lib/Calendar/Indonesia/Holiday.pm  view on Meta::CPAN

            eng_aliases => {
                schema     => ['array*'=>{of=>'str*'}],
                summary    => 'English other names, if any',
                pos        => 7,
            },
            ind_aliases => {
                schema     => ['array*'=>{of=>'str*'}],
                summary    => 'Indonesian other names, if any',
                pos        => 8,
            },
            is_holiday => {
                schema     => 'bool*',
                pos        => 9,
            },
            is_joint_leave => {
                schema     => 'bool*',
                summary    => 'Whether this date is a joint leave day '.
                    '("cuti bersama")',
                pos        => 10,
            },
            decree_date => {
                schema     => 'str',
                pos        => 11,
            },
            decree_note => {
                schema     => 'str',
                pos        => 12,
            },
            note => {
                schema     => 'str',
                pos        => 13,
            },
            tags => {
                schema     => 'array*',
                pos        => 14,
            },
        },
        pk => 'date',
    },
    langs => ['en_US', 'id_ID'],
);

die "BUG: Can't generate func: $res->[0] - $res->[1]"
    unless $res->[0] == 200;

delete $SPEC{list_idn_holidays}{args}{queries}{pos};
delete $SPEC{list_idn_holidays}{args}{queries}{slurpy};
$SPEC{list_idn_holidays}{args}{year}{pos}  = 0;
$SPEC{list_idn_holidays}{args}{month}{pos} = 1;

my $TEXT_AVAILABLE_YEARS = "Contains data from years $min_year to $max_year";

my $TEXT_WORKDAY_DEFINITION = <<'_';
Working day is defined as day that is not Saturday*/Sunday/holiday/joint leave
days*. If work_saturdays is set to true, Saturdays are also counted as working
days. If observe_joint_leaves is set to false, joint leave days are also counted
as working days.
_

    my $meta = $res->[2]{meta};
$meta->{summary} = "List Indonesian holidays in calendar";
$meta->{description} = <<"_";

List holidays and joint leave days ("cuti bersama").

$TEXT_AVAILABLE_YEARS

_

sub _check_date_arg {
    my ($date) = @_;
    if (ref($date) && $date->isa('DateTime')) {
        return $date;
    } elsif ($date =~ /\A(\d{4})-(\d{2})-(\d{2})\z/) {
        return DateTime->new(year=>$1, month=>$2, day=>$3);
    } else {
        return;
    }
}

$SPEC{list_idn_workdays} = {
    v => 1.1,
    summary => 'List working days (non-holiday business days) for a certain period',
    description => <<"_",

$TEXT_WORKDAY_DEFINITION

$TEXT_AVAILABLE_YEARS

_
    args => {
        start_date => {
            summary => 'Starting date',
            schema  => 'str*',
            pos => 0,
            description => <<'_',

Defaults to start of current month. Either a string in the form of "YYYY-MM-DD",
or a DateTime object, is accepted.

_
        },
        end_date => {
            summary => 'End date',
            schema  => 'str*',
            pos => 1,
            description => <<'_',

Defaults to end of current month. Either a string in the form of "YYYY-MM-DD",
or a DateTime object, is accepted.

_
        },
        work_saturdays => {
            schema  => ['bool' => {default=>0}],
            summary => 'If set to 1, Saturday is a working day',
        },
        observe_joint_leaves => {
            summary => 'If set to 0, do not observe joint leave as holidays',
            schema  => ['bool' => {default => 1}],
            cmdline_aliases => {j=>{}},

lib/Calendar/Indonesia/Holiday.pm  view on Meta::CPAN


Calendar::Indonesia::Holiday - List Indonesian public holidays

=head1 VERSION

This document describes version 0.355 of Calendar::Indonesia::Holiday (from Perl distribution Calendar-Indonesia-Holiday), released on 2025-12-19.

=head1 SYNOPSIS

 use Calendar::Indonesia::Holiday qw(
     list_idn_holidays
     list_idn_workdays

     count_idn_workdays

     is_idn_holiday
     is_idn_workday
 );

This lists Indonesian holidays for the year 2011, without the joint leave days
("cuti bersama"), showing only the dates:

 my $res = list_idn_holidays(year => 2011, is_joint_leave=>0);

Sample result:

 [200, "OK", [
   '2011-01-01',
   '2011-02-03',
   '2011-02-16',
   '2011-03-05',
   '2011-04-22',
   '2011-05-17',
   '2011-06-02',
   '2011-06-29',
   '2011-08-17',
   '2011-08-31',
   '2011-09-01',
   '2011-11-07',
   '2011-11-27',
   '2011-12-25',
 ]];

This lists religious Indonesian holidays, showing full details:

 my $res = list_idn_holidays(year => 2011,
                             "tags.has" => ['religious'], detail=>1);

Sample result:

 [200, "OK", [
   {date        => '2011-02-16',
    day         => 16,
    month       => 2,
    year        => 2011,
    ind_name    => 'Maulid Nabi Muhammad',
    eng_name    => 'Mawlid',
    eng_aliases => ['Mawlid An-Nabi'],
    ind_aliases => ['Maulud'],
    is_holiday  => 1,
    tags        => [qw/religious religion=islam calendar=lunar/],
   },
   ...
 ]];

This checks whether 2011-02-16 is a holiday:

 my $res = is_idn_holiday(date => '2011-02-16');
 print "2011-02-16 is a holiday\n" if $res->[2];

This checks whether 2021-03-11 is a working day:

 my $res = is_idn_workday(date => '2021-03-11');
 print "2011-02-16 is a holiday\n" if $res->[2];

This lists working days for a certain period:

 my $res = list_idn_workdays(start_date=>'2021-01-01', end_date=>'2021-06-30');

Idem, but returns a number instead. If unspecified, C<start_date> defaults to
start of current month and C<end_date> defaults to end of current month. So this
returns the number of working days in the current month:

 my $res = count_idn_workdays();

=head1 DESCRIPTION

This module provides functions to list Indonesian holidays. There is a
command-line script interface for this module: L<list-idn-holidays> and a few
others distributed in L<App::IndonesianHolidayUtils> distribution.

Calendar years supported: 1990-2026.

Note: Note that sometimes the holiday (as set by law) falls at a different date
than the actual religious commemoration date. When you use the C<detail> option,
the C<original_date> key will show you the actual religious date.

Note: it is also possible that multiple (religious, cultural) holidays fall on
the same national holiday. An example is May 8, 1997 which is commemorated as
Hijra 1418H as well as Ascension Day. When this happens, the C<holidays> key
will contain the details of each religious/cultural holiday.

Caveat: aside from national holidays, some provinces sometimes declare their own
(e.g. governor election day for East Java province, etc). This is currently not
yet included in this module.

=head1 DEVELOPER NOTES

To mark that a holiday has been moved from its original date, use the
C<original_date> option. For example, Mawlid in 2021 has been moved from its
original date 2021-11-19 (this is the day it is actually observed/commemorated)
to 2021-11-20 (this is the day the holiday is in effect where offices and public
places are closed). By adding this option, the summary will reflect this
information:

 date: 2021-12-20
 eng_name: Mawlid (commemorated on 2021-12-19)
 ind_name: Maulid Nabi Muhammad (diperingati 2021-12-19)

=head1 FUNCTIONS

lib/Calendar/Indonesia/Holiday.pm  view on Meta::CPAN


Working day is defined as day that is not Saturday*/Sunday/holiday/joint leave
days*. If work_saturdays is set to true, Saturdays are also counted as working
days. If observe_joint_leaves is set to false, joint leave days are also counted
as working days.

Contains data from years 1990 to 2026

This function is not exported by default, but exportable.

Arguments ('*' denotes required arguments):

=over 4

=item * B<date> => I<str>

(No description)

=item * B<day> => I<int>

(No description)

=item * B<month> => I<int>

(No description)

=item * B<observe_joint_leaves> => I<bool> (default: 1)

If set to 0, do not observe joint leave as holidays.

=item * B<work_saturdays> => I<bool> (default: 0)

If set to 1, Saturday is a working day.

=item * B<year> => I<int>

(No description)


=back

Returns an enveloped result (an array).

First element ($status_code) is an integer containing HTTP-like status code
(200 means OK, 4xx caller error, 5xx function error). Second element
($reason) is a string containing error message, or something like "OK" if status is
200. Third element ($payload) is the actual result, but usually not present when enveloped result is an error response ($status_code is not 2xx). Fourth
element (%result_meta) is called result metadata and is optional, a hash
that contains extra information, much like how HTTP response headers provide additional metadata.

Return value:  (any)



=head2 list_idn_holidays

Usage:

 list_idn_holidays(%args) -> [$status_code, $reason, $payload, \%result_meta]

List Indonesian holidays in calendar.

List holidays and joint leave days ("cuti bersama").

Contains data from years 1990 to 2026

This function is not exported by default, but exportable.

Arguments ('*' denotes required arguments):

=over 4

=item * B<date> => I<date>

Only return records where the 'date' field equals specified value.

=item * B<date.in> => I<array[date]>

Only return records where the 'date' field is in the specified values.

=item * B<date.is> => I<date>

Only return records where the 'date' field equals specified value.

=item * B<date.isnt> => I<date>

Only return records where the 'date' field does not equal specified value.

=item * B<date.max> => I<date>

Only return records where the 'date' field is less than or equal to specified value.

=item * B<date.min> => I<date>

Only return records where the 'date' field is greater than or equal to specified value.

=item * B<date.not_in> => I<array[date]>

Only return records where the 'date' field is not in the specified values.

=item * B<date.xmax> => I<date>

Only return records where the 'date' field is less than specified value.

=item * B<date.xmin> => I<date>

Only return records where the 'date' field is greater than specified value.

=item * B<day> => I<int>

Only return records where the 'day' field equals specified value.

=item * B<day.in> => I<array[int]>

Only return records where the 'day' field is in the specified values.

=item * B<day.is> => I<int>

Only return records where the 'day' field equals specified value.

=item * B<day.isnt> => I<int>

lib/Calendar/Indonesia/Holiday.pm  view on Meta::CPAN


Arguments ('*' denotes required arguments):

=over 4

=item * B<end_date> => I<str>

End date.

Defaults to end of current month. Either a string in the form of "YYYY-MM-DD",
or a DateTime object, is accepted.

=item * B<observe_joint_leaves> => I<bool> (default: 1)

If set to 0, do not observe joint leave as holidays.

=item * B<start_date> => I<str>

Starting date.

Defaults to start of current month. Either a string in the form of "YYYY-MM-DD",
or a DateTime object, is accepted.

=item * B<work_saturdays> => I<bool> (default: 0)

If set to 1, Saturday is a working day.


=back

Returns an enveloped result (an array).

First element ($status_code) is an integer containing HTTP-like status code
(200 means OK, 4xx caller error, 5xx function error). Second element
($reason) is a string containing error message, or something like "OK" if status is
200. Third element ($payload) is the actual result, but usually not present when enveloped result is an error response ($status_code is not 2xx). Fourth
element (%result_meta) is called result metadata and is optional, a hash
that contains extra information, much like how HTTP response headers provide additional metadata.

Return value:  (any)

=for Pod::Coverage ^(.+_id_.+)$

=head1 FAQ

=head2 What is "joint leave"?

Workers are normally granted around 12 days of paid leave per year (excluding
special leaves like maternity, etc). They are free to spend them on whichever
days they want. The joint leave ("cuti bersama") is a government program to
recommend that some of these leave days be spent together nationally on certain
assigned days, especially adjacent to holidays like Eid Ul-Fitr ("Lebaran"). It
is not mandated (companies can opt to follow it or not, depending on their
specific situation), but many do follow it anyway, e.g. government civil
workers, banks, etc. I am marking joint leave days with is_joint_leave=1 and
is_holiday=0, while the holidays themselves with is_holiday=1, so you can
differentiate/select both/either one.

=head2 When was joint leave established?

Joint leave was first decreed in 2001 [1] for the 2002 & 2003 calendar years.
The 2001 calendar year does not yet have joint leave days [2]. See also [3].
Websites that list joint leave days for 2001 or earlier years (example: [4],
[5]) are incorrect; by 2001 or earlier, these joint leave days had not been
officially decreed by the government.

[1] https://jdih.kemnaker.go.id/data_wirata/2002-4-4.pdf

[2] https://peraturan.bkpm.go.id/jdih/userfiles/batang/Kepmenag_162_2000.pdf

[3] http://www.wikiapbn.org/cuti-bersama/

[4] https://kalenderindonesia.com/libur/masehi/2001

[5] https://kalenderindonesia.com/libur/masehi/1991

=head2 What happens when multiple religious/holidays coincide on a single calendar day?

For example, in 1997, both Hijra and Ascension Day fall on May 8th. When this
happens, C<ind_name> and C<eng_name> will contain all the names of the holidays
separated by comma, respectively:

 Tahun Baru Hijriah, Kenaikan Isa Al-Masih
 Hijra, Ascension Day

All the properties that have the same value will be set in the merged holiday
data:

 is_holiday => 1,
 is_joint_leave => 1,

The C<multiple> property will also be set to true:

 multiple => 1,

All the tags will be merged:

 tags => ['religious', 'religion=christianity', 'calendar=lunar']

You can get each holiday's data in the C<holidays> key.

=head2 Data for older holidays?

Will be provided if there is demand and data source.

=head2 Holidays after (current year)+1?

Some religious holidays, especially Vesakha, are not determined yet. Joint leave
days are also usually decreed by the government in as late as October/November
in the preceding year.

=head2 How to calculate the difference of two dates in number of working days?

Use L</count_idn_workdays>.

=head1 HOMEPAGE

Please visit the project's homepage at L<https://metacpan.org/release/Calendar-Indonesia-Holiday>.

=head1 SOURCE

Source repository is at L<https://github.com/perlancar/perl-Calendar-Indonesia-Holiday>.

=head1 AUTHOR

perlancar <perlancar@cpan.org>

=head1 CONTRIBUTOR

=for stopwords Steven Haryanto

Steven Haryanto <stevenharyanto@gmail.com>

=head1 CONTRIBUTING


To contribute, you can send patches by email/via RT, or send pull requests on
GitHub.

Most of the time, you don't need to build the distribution yourself. You can
simply modify the code, then test via:

 % prove -l

If you want to build the distribution (e.g. to try to install it locally on your
system), you can install L<Dist::Zilla>,
L<Dist::Zilla::PluginBundle::Author::PERLANCAR>,
L<Pod::Weaver::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
Dist::Zilla- and/or Pod::Weaver plugins. Any additional steps required beyond
that are considered a bug and can be reported to me.

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2025 by perlancar <perlancar@cpan.org>.

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



( run in 1.262 second using v1.01-cache-2.11-cpan-54e63673c56 )