This file is indexed.

/usr/share/perl5/DateTime/TimeZone/OlsonDB/Observance.pm is in libdatetime-timezone-perl 1:2.18-1+2018d.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
package DateTime::TimeZone::OlsonDB::Observance;

use strict;
use warnings;
use namespace::autoclean;

our $VERSION = '2.18';

use DateTime::Duration;
use DateTime::TimeZone::OlsonDB;
use DateTime::TimeZone::OlsonDB::Change;
use List::Util 1.33 qw( any first );

sub new {
    my $class = shift;
    my %p     = @_;

    $p{until} ||= q{};
    $p{$_} ||= 0
        for qw( offset_from_std last_offset_from_std last_offset_from_utc );

    my $offset_from_utc
        = $p{gmtoff} =~ m/^[+-]?\d?\d$/ # only hours? need to handle specially
        ? 3600 * $p{gmtoff}
        : DateTime::TimeZone::offset_as_seconds( $p{gmtoff} );

    my $offset_from_std
        = DateTime::TimeZone::offset_as_seconds( $p{offset_from_std} );

    my $last_offset_from_utc = delete $p{last_offset_from_utc};
    my $last_offset_from_std = delete $p{last_offset_from_std};

    my $self = bless {
        %p,
        offset_from_utc => $offset_from_utc,
        offset_from_std => $offset_from_std,
        until           => [ split /\s+/, $p{until} ],
    }, $class;

    $self->{first_rule}
        = $self->_first_rule( $last_offset_from_utc, $last_offset_from_std );

    if ( $p{utc_start_datetime} ) {
        $offset_from_std += $self->{first_rule}->offset_from_std
            if $self->{first_rule};

        my $local_start_datetime = $p{utc_start_datetime}->clone;

        $local_start_datetime += DateTime::Duration->new(
            seconds => $offset_from_utc + $offset_from_std );

        $self->{local_start_datetime} = $local_start_datetime;
    }

    return $self;
}

sub offset_from_utc { $_[0]->{offset_from_utc} || 0 }
sub offset_from_std { $_[0]->{offset_from_std} || 0 }
sub total_offset    { $_[0]->offset_from_utc + $_[0]->offset_from_std }

sub rules      { @{ $_[0]->{rules} } }
sub first_rule { $_[0]->{first_rule} }

## no critic (Subroutines::ProhibitBuiltinHomonyms)
sub format { $_[0]->{format} }
## use critic

sub utc_start_datetime   { $_[0]->{utc_start_datetime} }
sub local_start_datetime { $_[0]->{local_start_datetime} }

sub formatted_short_name {
    my $self   = shift;
    my $letter = shift;

    my $format = $self->format;
    return $format unless $format =~ /%/;

    return sprintf( $format, $letter );
}

sub expand_from_rules {
    my $self = shift;
    my $zone = shift;

    # real max is year + 1 so we include max year
    my $max_year = (shift) + 1;

    my $min_year;

    if ( $self->utc_start_datetime ) {
        $min_year = $self->utc_start_datetime->year;
    }
    else {

        # There is at least one time zone that has an infinite
        # observance, but that observance has rules that only start at
        # a certain point - Pacific/Chatham

        # In this case we just find the earliest rule and start there

        $min_year
            = ( sort { $a <=> $b } map { $_->min_year } $self->rules )[0];
    }

    my $until = $self->until( $zone->last_change->offset_from_std );
    if ($until) {
        $max_year = $until->year;
    }
    else {

        # Some zones, like Asia/Tehran, have a predefined fixed set of
        # rules that go well into the future (2037 for Asia/Tehran)
        my $max_rule_year = 0;
        foreach my $rule ( $self->rules ) {
            $max_rule_year = $rule->max_year
                if $rule->max_year && $rule->max_year > $max_rule_year;
        }

        $max_year = $max_rule_year if $max_rule_year > $max_year;
    }

    foreach my $year ( $min_year .. $max_year ) {
        my @rules = $self->_sorted_rules_for_year($year);

        for my $rule (@rules) {
            my $dt = $rule->utc_start_datetime_for_year(
                $year,
                $self->offset_from_utc, $zone->last_change->offset_from_std
            );

            next
                if $self->utc_start_datetime
                && $dt <= $self->utc_start_datetime;

            ## no critic (Variables::ProhibitReusedNames)
            my $until = $self->until( $zone->last_change->offset_from_std );

            next if $until && $dt >= $until;

            my $change = DateTime::TimeZone::OlsonDB::Change->new(
                type                 => 'rule',
                utc_start_datetime   => $dt,
                local_start_datetime => $dt + DateTime::Duration->new(
                    seconds => $self->total_offset + $rule->offset_from_std
                ),
                short_name => $self->formatted_short_name( $rule->letter ),
                observance => $self,
                rule       => $rule,
            );

            if ($DateTime::TimeZone::OlsonDB::DEBUG) {
                ## no critic (InputOutput::RequireCheckedSyscalls)
                print "Adding rule change ...\n";

                $change->_debug_output;
            }

            $zone->add_change($change);
        }
    }
}

sub _sorted_rules_for_year {
    my $self = shift;
    my $year = shift;

    ## no critic (BuiltinFunctions::ProhibitComplexMappings)
    my @rules = (
        map      { $_->[0] }
            sort { $a->[1] <=> $b->[1] }
            map {
            my $dt = $_->utc_start_datetime_for_year(
                $year,
                $self->offset_from_utc, 0
            );
            [ $_, $dt ]
            }
            grep {
            $_->min_year <= $year
                && ( ( !$_->max_year ) || $_->max_year >= $year )
            } $self->rules
    );

    my %rules_by_month;
    for my $rule (@rules) {
        push @{ $rules_by_month{ $rule->month() } }, $rule;
    }

    # In some cases we have both a "max year" rule and a "this year" rule for
    # a given month's change. In that case, we want to pick the more specific
    # ("this year") rule, not apply both. This only matters for zones that
    # have a winter transition that follows the Islamic calendar to deal with
    # Ramadan. So far this has happened with Cairo, El_Aaiun, and other zones
    # in northern Africa.
    my @final_rules;
    for my $month ( sort { $a <=> $b } keys %rules_by_month ) {
        my @r = @{ $rules_by_month{$month} };
        if ( @r == 2 ) {
            my ($repeating) = grep { !defined $_->max_year() } @r;
            my ($this_year)
                = grep { $_->max_year() && $_->max_year() == $year } @r;
            if ( $repeating && $this_year ) {

                # We used to pick the repeating rule for year 2037 only
                # because it seemed like that's what zic did in the past. Now
                # it seems to pick the "this year" rule instead.
                if ($DateTime::TimeZone::OlsonDB::DEBUG) {
                    ## no critic (InputOutput::RequireCheckedSyscalls)
                    print
                        "Found two rules for the same month, picking the one for this year\n";
                }

                push @final_rules, $this_year;
                next;
            }

            push @final_rules, @r;
        }
        else {
            push @final_rules, @r;
        }
    }

    return @final_rules;
}

## no critic (Subroutines::ProhibitBuiltinHomonyms)
sub until {
    my $self = shift;
    my $offset_from_std = shift || $self->offset_from_std;

    return unless defined $self->until_year;

    my $utc = DateTime::TimeZone::OlsonDB::utc_datetime_for_time_spec(
        spec            => $self->until_time_spec,
        year            => $self->until_year,
        month           => $self->until_month,
        day             => $self->until_day,
        offset_from_utc => $self->offset_from_utc,
        offset_from_std => $offset_from_std,
    );

    return $utc;
}
## use critic

sub until_year { $_[0]->{until}[0] }

sub until_month {
    (
        defined $_[0]->{until}[1]
        ? $DateTime::TimeZone::OlsonDB::MONTHS{ $_[0]->{until}[1] }
        : 1
    );
}

sub until_day {
    (
        defined $_[0]->{until}[2]
        ? DateTime::TimeZone::OlsonDB::parse_day_spec(
            $_[0]->{until}[2], $_[0]->until_month, $_[0]->until_year
            )
        : 1
    );
}

sub until_time_spec {
    defined $_[0]->{until}[3] ? $_[0]->{until}[3] : '00:00:00';
}

## no critic (Subroutines::ProhibitExcessComplexity)
sub _first_rule {
    my $self                 = shift;
    my $last_offset_from_utc = shift;
    my $last_offset_from_std = shift;

    return unless $self->rules;

    my $date = $self->utc_start_datetime
        or return $self->_first_no_dst_rule;

    my @rules = $self->rules;

    my %possible_rules;

    my $year = $date->year;
    foreach my $rule (@rules) {

        # We need to look at what the year _would_ be if we added the
        # rule's offset to the UTC date.  Otherwise we can end up with
        # a UTC date in year X, and a rule that starts in _local_ year
        # X + 1, where that rule really does apply to that UTC date.
        my $temp_year
            = $date->clone->add(
            seconds => $self->offset_from_utc + $rule->offset_from_std )
            ->year;

        # Save the highest value
        $year = $temp_year if $temp_year > $year;

        next if $rule->min_year > $temp_year;

        $possible_rules{$rule} = $rule;
    }

    my $earliest_year = $year - 1;
    foreach my $rule (@rules) {
        $earliest_year = $rule->min_year
            if $rule->min_year < $earliest_year;
    }

    # figure out what date each rule would start on _if_ that rule
    # were applied to this current observance.  this could be a rule
    # that started much earlier, but is only now active because of an
    # observance switch.  An obnoxious example of this is
    # America/Phoenix in 1944, which applies the US rule in April,
    # thus (re-)instating the "war time" rule from 1942.  Can you say
    # ridiculous crack-smoking stupidity?
    my @rule_dates;
    foreach my $y ( $earliest_year .. $year ) {
    RULE:
        foreach my $rule ( values %possible_rules ) {

            # skip rules that can't have applied the year before the
            # observance started.
            if ( $rule->min_year > $y ) {
                ## no critic (InputOutput::RequireCheckedSyscalls)
                print 'Skipping rule beginning in ', $rule->min_year,
                    ".  Year is $y.\n"
                    if $DateTime::TimeZone::OlsonDB::DEBUG;

                next RULE;
            }

            if ( $rule->max_year && $rule->max_year < $y ) {
                ## no critic (InputOutput::RequireCheckedSyscalls)
                print 'Skipping rule ending in ', $rule->max_year,
                    ".     Year is $y.\n"
                    if $DateTime::TimeZone::OlsonDB::DEBUG;

                next RULE;
            }

            my $rule_start = $rule->utc_start_datetime_for_year(
                $y,
                $last_offset_from_utc, $last_offset_from_std
            );

            push @rule_dates, [ $rule_start, $rule ];
        }
    }

    @rule_dates = sort { $a->[0] <=> $b->[0] } @rule_dates;

    ## no critic (InputOutput::RequireCheckedSyscalls)
    print "Looking for first rule ...\n"
        if $DateTime::TimeZone::OlsonDB::DEBUG;
    print ' Observance starts: ', $date->datetime, "\n\n"
        if $DateTime::TimeZone::OlsonDB::DEBUG;
    ## use critic

    # ... look through the rules to see if any are still in
    # effect at the beginning of the observance

    ## no critic (ControlStructures::ProhibitCStyleForLoops)
    for ( my $x = 0; $x < @rule_dates; $x++ ) {
        my ( $dt, $rule ) = @{ $rule_dates[$x] };
        my ( $next_dt, $next_rule )
            = $x < @rule_dates - 1 ? @{ $rule_dates[ $x + 1 ] } : undef;

        next if $next_dt && $next_dt < $date;

        ## no critic (InputOutput::RequireCheckedSyscalls)
        print ' This rule starts:  ', $dt->datetime, "\n"
            if $DateTime::TimeZone::OlsonDB::DEBUG;

        print ' Next rule starts:  ', $next_dt->datetime, "\n"
            if $next_dt && $DateTime::TimeZone::OlsonDB::DEBUG;

        print ' No next rule\n\n'
            if !$next_dt && $DateTime::TimeZone::OlsonDB::DEBUG;
        ## use critic

        if ( $dt <= $date ) {
            if ($next_dt) {
                return $rule if $date < $next_dt;
                return $next_rule if $date == $next_dt;
            }
            else {
                return $rule;
            }
        }
    }

    # If this observance has rules, but the rules don't have any
    # defined changes until after the observance starts, we get the
    # earliest standard time rule and use it. If there is none, shit
    # blows up (but this is not the case for any time zones as of
    # 2009a). I really, really hate the Olson database a lot of the
    # time! Could this be more arbitrary?
    my $std_time_rule = $self->_first_no_dst_rule;

    die
        q{Cannot find a rule that applies to the observance's date range and cannot find a rule without DST to apply}
        unless $std_time_rule;

    return $std_time_rule;
}
## use critic

sub _first_no_dst_rule {
    my $self = shift;

    return first { !$_->offset_from_std }
    sort { $a->min_year <=> $b->min_year } $self->rules;
}

1;