tvl-depot/absl/time/internal/cctz/src/time_zone_info.cc

976 lines
37 KiB
C++
Raw Normal View History

// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
Export of internal Abseil changes. -- 00d42e3d5433aaf29c2ed293520b2ba178ae8bdb by Greg Falcon <gfalcon@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 238061818 -- 867a7ca318fac2991ea9a4107dbae3cc9fbf974a by Abseil Team <absl-team@google.com>: Added a IWYU export pragma when including a standard header for the purpose of aliasing its symbols. PiperOrigin-RevId: 238022277 -- 17047745058f2f151cd986ea9f649512542d3876 by Matt Armstrong <marmstrong@google.com>: Clarify the comment discouraging WrapUnique<T>(x) calls. PiperOrigin-RevId: 237873803 -- 3dcb2e4968243d33ca0ce53280c445df50f4a7ec by Samuel Benzaquen <sbenza@google.com>: Workaround clang bug https://bugs.llvm.org/show_bug.cgi?id=38289 PiperOrigin-RevId: 237873551 -- f348d2dc7087a990cbdfb95aa51fd7ff478ae40e by Samuel Benzaquen <sbenza@google.com>: Reduce minimum capacity to 1. This reduces memory usage for small tables. A flat_hash_set<int> of 1 element goes from 92 bytes to 24. A flat_hash_set<string> of 1 element goes from 512 bytes to 56. PiperOrigin-RevId: 237859811 -- 9c8125be5e4e5d22a7bb62bdec8c323338385c1b by Jon Cohen <cohenjon@google.com>: Bump to CMake 3.5. This is the oldest modern cmake being included by default in most popular OS distributions according to https://repology.org/project/cmake/versions. Specifically, Ubuntu LTS 16.04 uses cmake 3.5 (https://packages.ubuntu.com/xenial/cmake) PiperOrigin-RevId: 237859345 -- 07638d672e0a4dced986a62750cfd8318ed36ffa by Derek Mauro <dmauro@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 237714597 GitOrigin-RevId: 00d42e3d5433aaf29c2ed293520b2ba178ae8bdb Change-Id: I5faecc45add4a5a774d4f9baf06e5519091f2ccc
2019-03-12 19:39:15 +01:00
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This file implements the TimeZoneIf interface using the "zoneinfo"
// data provided by the IANA Time Zone Database (i.e., the only real game
// in town).
//
// TimeZoneInfo represents the history of UTC-offset changes within a time
// zone. Most changes are due to daylight-saving rules, but occasionally
// shifts are made to the time-zone's base offset. The database only attempts
// to be definitive for times since 1970, so be wary of local-time conversions
// before that. Also, rule and zone-boundary changes are made at the whim
// of governments, so the conversion of future times needs to be taken with
// a grain of salt.
//
// For more information see tzfile(5), http://www.iana.org/time-zones, or
Export of internal Abseil changes. -- 00d42e3d5433aaf29c2ed293520b2ba178ae8bdb by Greg Falcon <gfalcon@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 238061818 -- 867a7ca318fac2991ea9a4107dbae3cc9fbf974a by Abseil Team <absl-team@google.com>: Added a IWYU export pragma when including a standard header for the purpose of aliasing its symbols. PiperOrigin-RevId: 238022277 -- 17047745058f2f151cd986ea9f649512542d3876 by Matt Armstrong <marmstrong@google.com>: Clarify the comment discouraging WrapUnique<T>(x) calls. PiperOrigin-RevId: 237873803 -- 3dcb2e4968243d33ca0ce53280c445df50f4a7ec by Samuel Benzaquen <sbenza@google.com>: Workaround clang bug https://bugs.llvm.org/show_bug.cgi?id=38289 PiperOrigin-RevId: 237873551 -- f348d2dc7087a990cbdfb95aa51fd7ff478ae40e by Samuel Benzaquen <sbenza@google.com>: Reduce minimum capacity to 1. This reduces memory usage for small tables. A flat_hash_set<int> of 1 element goes from 92 bytes to 24. A flat_hash_set<string> of 1 element goes from 512 bytes to 56. PiperOrigin-RevId: 237859811 -- 9c8125be5e4e5d22a7bb62bdec8c323338385c1b by Jon Cohen <cohenjon@google.com>: Bump to CMake 3.5. This is the oldest modern cmake being included by default in most popular OS distributions according to https://repology.org/project/cmake/versions. Specifically, Ubuntu LTS 16.04 uses cmake 3.5 (https://packages.ubuntu.com/xenial/cmake) PiperOrigin-RevId: 237859345 -- 07638d672e0a4dced986a62750cfd8318ed36ffa by Derek Mauro <dmauro@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 237714597 GitOrigin-RevId: 00d42e3d5433aaf29c2ed293520b2ba178ae8bdb Change-Id: I5faecc45add4a5a774d4f9baf06e5519091f2ccc
2019-03-12 19:39:15 +01:00
// https://en.wikipedia.org/wiki/Zoneinfo.
//
// Note that we assume the proleptic Gregorian calendar and 60-second
// minutes throughout.
#include "time_zone_info.h"
#include <algorithm>
#include <cassert>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include "absl/time/internal/cctz/include/cctz/civil_time.h"
#include "time_zone_fixed.h"
#include "time_zone_posix.h"
namespace absl {
namespace time_internal {
namespace cctz {
namespace {
inline bool IsLeap(year_t year) {
return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0);
}
// The number of days in non-leap and leap years respectively.
const std::int_least32_t kDaysPerYear[2] = {365, 366};
// The day offsets of the beginning of each (1-based) month in non-leap and
// leap years respectively (e.g., 335 days before December in a leap year).
const std::int_least16_t kMonthOffsets[2][1 + 12 + 1] = {
{-1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
{-1, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366},
};
// We reject leap-second encoded zoneinfo and so assume 60-second minutes.
const std::int_least32_t kSecsPerDay = 24 * 60 * 60;
// 400-year chunks always have 146097 days (20871 weeks).
const std::int_least64_t kSecsPer400Years = 146097LL * kSecsPerDay;
// Like kDaysPerYear[] but scaled up by a factor of kSecsPerDay.
const std::int_least32_t kSecsPerYear[2] = {
365 * kSecsPerDay,
366 * kSecsPerDay,
};
// Single-byte, unsigned numeric values are encoded directly.
inline std::uint_fast8_t Decode8(const char* cp) {
return static_cast<std::uint_fast8_t>(*cp) & 0xff;
}
// Multi-byte, numeric values are encoded using a MSB first,
// twos-complement representation. These helpers decode, from
// the given address, 4-byte and 8-byte values respectively.
// Note: If int_fastXX_t == intXX_t and this machine is not
// twos complement, then there will be at least one input value
// we cannot represent.
std::int_fast32_t Decode32(const char* cp) {
std::uint_fast32_t v = 0;
for (int i = 0; i != (32 / 8); ++i) v = (v << 8) | Decode8(cp++);
const std::int_fast32_t s32max = 0x7fffffff;
const auto s32maxU = static_cast<std::uint_fast32_t>(s32max);
if (v <= s32maxU) return static_cast<std::int_fast32_t>(v);
return static_cast<std::int_fast32_t>(v - s32maxU - 1) - s32max - 1;
}
std::int_fast64_t Decode64(const char* cp) {
std::uint_fast64_t v = 0;
for (int i = 0; i != (64 / 8); ++i) v = (v << 8) | Decode8(cp++);
const std::int_fast64_t s64max = 0x7fffffffffffffff;
const auto s64maxU = static_cast<std::uint_fast64_t>(s64max);
if (v <= s64maxU) return static_cast<std::int_fast64_t>(v);
return static_cast<std::int_fast64_t>(v - s64maxU - 1) - s64max - 1;
}
// Generate a year-relative offset for a PosixTransition.
std::int_fast64_t TransOffset(bool leap_year, int jan1_weekday,
const PosixTransition& pt) {
std::int_fast64_t days = 0;
switch (pt.date.fmt) {
case PosixTransition::J: {
days = pt.date.j.day;
if (!leap_year || days < kMonthOffsets[1][3]) days -= 1;
break;
}
case PosixTransition::N: {
days = pt.date.n.day;
break;
}
case PosixTransition::M: {
const bool last_week = (pt.date.m.week == 5);
days = kMonthOffsets[leap_year][pt.date.m.month + last_week];
const std::int_fast64_t weekday = (jan1_weekday + days) % 7;
if (last_week) {
days -= (weekday + 7 - 1 - pt.date.m.weekday) % 7 + 1;
} else {
days += (pt.date.m.weekday + 7 - weekday) % 7;
days += (pt.date.m.week - 1) * 7;
}
break;
}
}
return (days * kSecsPerDay) + pt.time.offset;
}
inline time_zone::civil_lookup MakeUnique(const time_point<seconds>& tp) {
time_zone::civil_lookup cl;
cl.kind = time_zone::civil_lookup::UNIQUE;
cl.pre = cl.trans = cl.post = tp;
return cl;
}
inline time_zone::civil_lookup MakeUnique(std::int_fast64_t unix_time) {
return MakeUnique(FromUnixSeconds(unix_time));
}
inline time_zone::civil_lookup MakeSkipped(const Transition& tr,
const civil_second& cs) {
time_zone::civil_lookup cl;
cl.kind = time_zone::civil_lookup::SKIPPED;
cl.pre = FromUnixSeconds(tr.unix_time - 1 + (cs - tr.prev_civil_sec));
cl.trans = FromUnixSeconds(tr.unix_time);
cl.post = FromUnixSeconds(tr.unix_time - (tr.civil_sec - cs));
return cl;
}
inline time_zone::civil_lookup MakeRepeated(const Transition& tr,
const civil_second& cs) {
time_zone::civil_lookup cl;
cl.kind = time_zone::civil_lookup::REPEATED;
cl.pre = FromUnixSeconds(tr.unix_time - 1 - (tr.prev_civil_sec - cs));
cl.trans = FromUnixSeconds(tr.unix_time);
cl.post = FromUnixSeconds(tr.unix_time + (cs - tr.civil_sec));
return cl;
}
inline civil_second YearShift(const civil_second& cs, year_t shift) {
return civil_second(cs.year() + shift, cs.month(), cs.day(),
cs.hour(), cs.minute(), cs.second());
}
} // namespace
// What (no leap-seconds) UTC+seconds zoneinfo would look like.
bool TimeZoneInfo::ResetToBuiltinUTC(const seconds& offset) {
transition_types_.resize(1);
TransitionType& tt(transition_types_.back());
tt.utc_offset = static_cast<std::int_least32_t>(offset.count());
tt.is_dst = false;
tt.abbr_index = 0;
// We temporarily add some redundant, contemporary (2013 through 2023)
// transitions for performance reasons. See TimeZoneInfo::LocalTime().
// TODO: Fix the performance issue and remove the extra transitions.
transitions_.clear();
transitions_.reserve(12);
for (const std::int_fast64_t unix_time : {
-(1LL << 59), // BIG_BANG
1356998400LL, // 2013-01-01T00:00:00+00:00
1388534400LL, // 2014-01-01T00:00:00+00:00
1420070400LL, // 2015-01-01T00:00:00+00:00
1451606400LL, // 2016-01-01T00:00:00+00:00
1483228800LL, // 2017-01-01T00:00:00+00:00
1514764800LL, // 2018-01-01T00:00:00+00:00
1546300800LL, // 2019-01-01T00:00:00+00:00
1577836800LL, // 2020-01-01T00:00:00+00:00
1609459200LL, // 2021-01-01T00:00:00+00:00
1640995200LL, // 2022-01-01T00:00:00+00:00
1672531200LL, // 2023-01-01T00:00:00+00:00
2147483647LL, // 2^31 - 1
}) {
Transition& tr(*transitions_.emplace(transitions_.end()));
tr.unix_time = unix_time;
tr.type_index = 0;
tr.civil_sec = LocalTime(tr.unix_time, tt).cs;
tr.prev_civil_sec = tr.civil_sec - 1;
}
default_transition_type_ = 0;
abbreviations_ = FixedOffsetToAbbr(offset);
abbreviations_.append(1, '\0'); // add NUL
future_spec_.clear(); // never needed for a fixed-offset zone
extended_ = false;
tt.civil_max = LocalTime(seconds::max().count(), tt).cs;
tt.civil_min = LocalTime(seconds::min().count(), tt).cs;
transitions_.shrink_to_fit();
return true;
}
// Builds the in-memory header using the raw bytes from the file.
bool TimeZoneInfo::Header::Build(const tzhead& tzh) {
std::int_fast32_t v;
if ((v = Decode32(tzh.tzh_timecnt)) < 0) return false;
timecnt = static_cast<std::size_t>(v);
if ((v = Decode32(tzh.tzh_typecnt)) < 0) return false;
typecnt = static_cast<std::size_t>(v);
if ((v = Decode32(tzh.tzh_charcnt)) < 0) return false;
charcnt = static_cast<std::size_t>(v);
if ((v = Decode32(tzh.tzh_leapcnt)) < 0) return false;
leapcnt = static_cast<std::size_t>(v);
if ((v = Decode32(tzh.tzh_ttisstdcnt)) < 0) return false;
ttisstdcnt = static_cast<std::size_t>(v);
if ((v = Decode32(tzh.tzh_ttisgmtcnt)) < 0) return false;
ttisgmtcnt = static_cast<std::size_t>(v);
return true;
}
// How many bytes of data are associated with this header. The result
// depends upon whether this is a section with 4-byte or 8-byte times.
std::size_t TimeZoneInfo::Header::DataLength(std::size_t time_len) const {
std::size_t len = 0;
len += (time_len + 1) * timecnt; // unix_time + type_index
len += (4 + 1 + 1) * typecnt; // utc_offset + is_dst + abbr_index
len += 1 * charcnt; // abbreviations
len += (time_len + 4) * leapcnt; // leap-time + TAI-UTC
len += 1 * ttisstdcnt; // UTC/local indicators
len += 1 * ttisgmtcnt; // standard/wall indicators
return len;
}
// Check that the TransitionType has the expected offset/is_dst/abbreviation.
void TimeZoneInfo::CheckTransition(const std::string& name,
const TransitionType& tt,
std::int_fast32_t offset, bool is_dst,
const std::string& abbr) const {
if (tt.utc_offset != offset || tt.is_dst != is_dst ||
&abbreviations_[tt.abbr_index] != abbr) {
std::clog << name << ": Transition"
<< " offset=" << tt.utc_offset << "/"
<< (tt.is_dst ? "DST" : "STD")
<< "/abbr=" << &abbreviations_[tt.abbr_index]
<< " does not match POSIX spec '" << future_spec_ << "'\n";
}
}
// zic(8) can generate no-op transitions when a zone changes rules at an
// instant when there is actually no discontinuity. So we check whether
// two transitions have equivalent types (same offset/is_dst/abbr).
bool TimeZoneInfo::EquivTransitions(std::uint_fast8_t tt1_index,
std::uint_fast8_t tt2_index) const {
if (tt1_index == tt2_index) return true;
const TransitionType& tt1(transition_types_[tt1_index]);
const TransitionType& tt2(transition_types_[tt2_index]);
if (tt1.is_dst != tt2.is_dst) return false;
if (tt1.utc_offset != tt2.utc_offset) return false;
if (tt1.abbr_index != tt2.abbr_index) return false;
return true;
}
// Use the POSIX-TZ-environment-variable-style string to handle times
// in years after the last transition stored in the zoneinfo data.
void TimeZoneInfo::ExtendTransitions(const std::string& name,
const Header& hdr) {
extended_ = false;
bool extending = !future_spec_.empty();
PosixTimeZone posix;
if (extending && !ParsePosixSpec(future_spec_, &posix)) {
std::clog << name << ": Failed to parse '" << future_spec_ << "'\n";
extending = false;
}
if (extending && posix.dst_abbr.empty()) { // std only
// The future specification should match the last/default transition,
// and that means that handling the future will fall out naturally.
std::uint_fast8_t index = default_transition_type_;
if (hdr.timecnt != 0) index = transitions_[hdr.timecnt - 1].type_index;
const TransitionType& tt(transition_types_[index]);
CheckTransition(name, tt, posix.std_offset, false, posix.std_abbr);
extending = false;
}
if (extending && hdr.timecnt < 2) {
std::clog << name << ": Too few transitions for POSIX spec\n";
extending = false;
}
if (!extending) {
// Ensure that there is always a transition in the second half of the
// time line (the BIG_BANG transition is in the first half) so that the
// signed difference between a civil_second and the civil_second of its
// previous transition is always representable, without overflow.
const Transition& last(transitions_.back());
if (last.unix_time < 0) {
const std::uint_fast8_t type_index = last.type_index;
Transition& tr(*transitions_.emplace(transitions_.end()));
tr.unix_time = 2147483647; // 2038-01-19T03:14:07+00:00
tr.type_index = type_index;
}
return; // last transition wins
}
// Extend the transitions for an additional 400 years using the
// future specification. Years beyond those can be handled by
// mapping back to a cycle-equivalent year within that range.
// zic(8) should probably do this so that we don't have to.
// TODO: Reduce the extension by the number of compatible
// transitions already in place.
transitions_.reserve(hdr.timecnt + 400 * 2 + 1);
transitions_.resize(hdr.timecnt + 400 * 2);
extended_ = true;
// The future specification should match the last two transitions,
// and those transitions should have different is_dst flags. Note
// that nothing says the UTC offset used by the is_dst transition
// must be greater than that used by the !is_dst transition. (See
// Europe/Dublin, for example.)
const Transition* tr0 = &transitions_[hdr.timecnt - 1];
const Transition* tr1 = &transitions_[hdr.timecnt - 2];
const TransitionType* tt0 = &transition_types_[tr0->type_index];
const TransitionType* tt1 = &transition_types_[tr1->type_index];
const TransitionType& dst(tt0->is_dst ? *tt0 : *tt1);
const TransitionType& std(tt0->is_dst ? *tt1 : *tt0);
CheckTransition(name, dst, posix.dst_offset, true, posix.dst_abbr);
CheckTransition(name, std, posix.std_offset, false, posix.std_abbr);
// Add the transitions to tr1 and back to tr0 for each extra year.
last_year_ = LocalTime(tr0->unix_time, *tt0).cs.year();
bool leap_year = IsLeap(last_year_);
const civil_day jan1(last_year_, 1, 1);
std::int_fast64_t jan1_time = civil_second(jan1) - civil_second();
int jan1_weekday = (static_cast<int>(get_weekday(jan1)) + 1) % 7;
Transition* tr = &transitions_[hdr.timecnt]; // next trans to fill
if (LocalTime(tr1->unix_time, *tt1).cs.year() != last_year_) {
// Add a single extra transition to align to a calendar year.
transitions_.resize(transitions_.size() + 1);
assert(tr == &transitions_[hdr.timecnt]); // no reallocation
const PosixTransition& pt1(tt0->is_dst ? posix.dst_end : posix.dst_start);
std::int_fast64_t tr1_offset = TransOffset(leap_year, jan1_weekday, pt1);
tr->unix_time = jan1_time + tr1_offset - tt0->utc_offset;
tr++->type_index = tr1->type_index;
tr0 = &transitions_[hdr.timecnt];
tr1 = &transitions_[hdr.timecnt - 1];
tt0 = &transition_types_[tr0->type_index];
tt1 = &transition_types_[tr1->type_index];
}
const PosixTransition& pt1(tt0->is_dst ? posix.dst_end : posix.dst_start);
const PosixTransition& pt0(tt0->is_dst ? posix.dst_start : posix.dst_end);
for (const year_t limit = last_year_ + 400; last_year_ < limit;) {
last_year_ += 1; // an additional year of generated transitions
jan1_time += kSecsPerYear[leap_year];
jan1_weekday = (jan1_weekday + kDaysPerYear[leap_year]) % 7;
leap_year = !leap_year && IsLeap(last_year_);
std::int_fast64_t tr1_offset = TransOffset(leap_year, jan1_weekday, pt1);
tr->unix_time = jan1_time + tr1_offset - tt0->utc_offset;
tr++->type_index = tr1->type_index;
std::int_fast64_t tr0_offset = TransOffset(leap_year, jan1_weekday, pt0);
tr->unix_time = jan1_time + tr0_offset - tt1->utc_offset;
tr++->type_index = tr0->type_index;
}
assert(tr == &transitions_[0] + transitions_.size());
}
bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) {
// Read and validate the header.
tzhead tzh;
if (zip->Read(&tzh, sizeof(tzh)) != sizeof(tzh))
return false;
if (strncmp(tzh.tzh_magic, TZ_MAGIC, sizeof(tzh.tzh_magic)) != 0)
return false;
Header hdr;
if (!hdr.Build(tzh))
return false;
std::size_t time_len = 4;
if (tzh.tzh_version[0] != '\0') {
// Skip the 4-byte data.
if (zip->Skip(hdr.DataLength(time_len)) != 0)
return false;
// Read and validate the header for the 8-byte data.
if (zip->Read(&tzh, sizeof(tzh)) != sizeof(tzh))
return false;
if (strncmp(tzh.tzh_magic, TZ_MAGIC, sizeof(tzh.tzh_magic)) != 0)
return false;
if (tzh.tzh_version[0] == '\0')
return false;
if (!hdr.Build(tzh))
return false;
time_len = 8;
}
if (hdr.typecnt == 0)
return false;
if (hdr.leapcnt != 0) {
// This code assumes 60-second minutes so we do not want
// the leap-second encoded zoneinfo. We could reverse the
// compensation, but the "right" encoding is rarely used
// so currently we simply reject such data.
return false;
}
if (hdr.ttisstdcnt != 0 && hdr.ttisstdcnt != hdr.typecnt)
return false;
if (hdr.ttisgmtcnt != 0 && hdr.ttisgmtcnt != hdr.typecnt)
return false;
// Read the data into a local buffer.
std::size_t len = hdr.DataLength(time_len);
std::vector<char> tbuf(len);
if (zip->Read(tbuf.data(), len) != len)
return false;
const char* bp = tbuf.data();
// Decode and validate the transitions.
transitions_.reserve(hdr.timecnt + 2); // We might add a couple.
transitions_.resize(hdr.timecnt);
for (std::size_t i = 0; i != hdr.timecnt; ++i) {
transitions_[i].unix_time = (time_len == 4) ? Decode32(bp) : Decode64(bp);
bp += time_len;
if (i != 0) {
// Check that the transitions are ordered by time (as zic guarantees).
if (!Transition::ByUnixTime()(transitions_[i - 1], transitions_[i]))
return false; // out of order
}
}
bool seen_type_0 = false;
for (std::size_t i = 0; i != hdr.timecnt; ++i) {
transitions_[i].type_index = Decode8(bp++);
if (transitions_[i].type_index >= hdr.typecnt)
return false;
if (transitions_[i].type_index == 0)
seen_type_0 = true;
}
// Decode and validate the transition types.
transition_types_.resize(hdr.typecnt);
for (std::size_t i = 0; i != hdr.typecnt; ++i) {
transition_types_[i].utc_offset =
static_cast<std::int_least32_t>(Decode32(bp));
if (transition_types_[i].utc_offset >= kSecsPerDay ||
transition_types_[i].utc_offset <= -kSecsPerDay)
return false;
bp += 4;
transition_types_[i].is_dst = (Decode8(bp++) != 0);
transition_types_[i].abbr_index = Decode8(bp++);
if (transition_types_[i].abbr_index >= hdr.charcnt)
return false;
}
// Determine the before-first-transition type.
default_transition_type_ = 0;
if (seen_type_0 && hdr.timecnt != 0) {
std::uint_fast8_t index = 0;
if (transition_types_[0].is_dst) {
index = transitions_[0].type_index;
while (index != 0 && transition_types_[index].is_dst)
--index;
}
while (index != hdr.typecnt && transition_types_[index].is_dst)
++index;
if (index != hdr.typecnt)
default_transition_type_ = index;
}
// Copy all the abbreviations.
abbreviations_.assign(bp, hdr.charcnt);
bp += hdr.charcnt;
// Skip the unused portions. We've already dispensed with leap-second
// encoded zoneinfo. The ttisstd/ttisgmt indicators only apply when
// interpreting a POSIX spec that does not include start/end rules, and
// that isn't the case here (see "zic -p").
bp += (8 + 4) * hdr.leapcnt; // leap-time + TAI-UTC
bp += 1 * hdr.ttisstdcnt; // UTC/local indicators
bp += 1 * hdr.ttisgmtcnt; // standard/wall indicators
assert(bp == tbuf.data() + tbuf.size());
future_spec_.clear();
if (tzh.tzh_version[0] != '\0') {
// Snarf up the NL-enclosed future POSIX spec. Note
// that version '3' files utilize an extended format.
auto get_char = [](ZoneInfoSource* zip) -> int {
unsigned char ch; // all non-EOF results are positive
return (zip->Read(&ch, 1) == 1) ? ch : EOF;
};
if (get_char(zip) != '\n')
return false;
for (int c = get_char(zip); c != '\n'; c = get_char(zip)) {
if (c == EOF)
return false;
future_spec_.push_back(static_cast<char>(c));
}
}
// We don't check for EOF so that we're forwards compatible.
// If we did not find version information during the standard loading
// process (as of tzh_version '3' that is unsupported), then ask the
// ZoneInfoSource for any out-of-bound version std::string it may be privy to.
if (version_.empty()) {
version_ = zip->Version();
}
// Trim redundant transitions. zic may have added these to work around
// differences between the glibc and reference implementations (see
// zic.c:dontmerge) and the Qt library (see zic.c:WORK_AROUND_QTBUG_53071).
// For us, they just get in the way when we do future_spec_ extension.
while (hdr.timecnt > 1) {
if (!EquivTransitions(transitions_[hdr.timecnt - 1].type_index,
transitions_[hdr.timecnt - 2].type_index)) {
break;
}
hdr.timecnt -= 1;
}
transitions_.resize(hdr.timecnt);
// Ensure that there is always a transition in the first half of the
// time line (the second half is handled in ExtendTransitions()) so that
// the signed difference between a civil_second and the civil_second of
// its previous transition is always representable, without overflow.
// A contemporary zic will usually have already done this for us.
if (transitions_.empty() || transitions_.front().unix_time >= 0) {
Transition& tr(*transitions_.emplace(transitions_.begin()));
tr.unix_time = -(1LL << 59); // see tz/zic.c "BIG_BANG"
tr.type_index = default_transition_type_;
hdr.timecnt += 1;
}
// Extend the transitions using the future specification.
ExtendTransitions(name, hdr);
// Compute the local civil time for each transition and the preceding
// second. These will be used for reverse conversions in MakeTime().
const TransitionType* ttp = &transition_types_[default_transition_type_];
for (std::size_t i = 0; i != transitions_.size(); ++i) {
Transition& tr(transitions_[i]);
tr.prev_civil_sec = LocalTime(tr.unix_time, *ttp).cs - 1;
ttp = &transition_types_[tr.type_index];
tr.civil_sec = LocalTime(tr.unix_time, *ttp).cs;
if (i != 0) {
// Check that the transitions are ordered by civil time. Essentially
// this means that an offset change cannot cross another such change.
// No one does this in practice, and we depend on it in MakeTime().
if (!Transition::ByCivilTime()(transitions_[i - 1], tr))
return false; // out of order
}
}
// Compute the maximum/minimum civil times that can be converted to a
// time_point<seconds> for each of the zone's transition types.
for (auto& tt : transition_types_) {
tt.civil_max = LocalTime(seconds::max().count(), tt).cs;
tt.civil_min = LocalTime(seconds::min().count(), tt).cs;
}
transitions_.shrink_to_fit();
return true;
}
namespace {
// fopen(3) adaptor.
inline FILE* FOpen(const char* path, const char* mode) {
#if defined(_MSC_VER)
FILE* fp;
if (fopen_s(&fp, path, mode) != 0) fp = nullptr;
return fp;
#else
return fopen(path, mode); // TODO: Enable the close-on-exec flag.
#endif
}
// A stdio(3)-backed implementation of ZoneInfoSource.
class FileZoneInfoSource : public ZoneInfoSource {
public:
static std::unique_ptr<ZoneInfoSource> Open(const std::string& name);
std::size_t Read(void* ptr, std::size_t size) override {
size = std::min(size, len_);
std::size_t nread = fread(ptr, 1, size, fp_.get());
len_ -= nread;
return nread;
}
int Skip(std::size_t offset) override {
offset = std::min(offset, len_);
int rc = fseek(fp_.get(), static_cast<long>(offset), SEEK_CUR);
if (rc == 0) len_ -= offset;
return rc;
}
std::string Version() const override {
// TODO: It would nice if the zoneinfo data included the tzdb version.
return std::string();
}
protected:
explicit FileZoneInfoSource(
FILE* fp, std::size_t len = std::numeric_limits<std::size_t>::max())
: fp_(fp, fclose), len_(len) {}
private:
std::unique_ptr<FILE, int(*)(FILE*)> fp_;
std::size_t len_;
};
std::unique_ptr<ZoneInfoSource> FileZoneInfoSource::Open(
const std::string& name) {
// Use of the "file:" prefix is intended for testing purposes only.
if (name.compare(0, 5, "file:") == 0) return Open(name.substr(5));
// Map the time-zone name to a path name.
std::string path;
if (name.empty() || name[0] != '/') {
const char* tzdir = "/usr/share/zoneinfo";
char* tzdir_env = nullptr;
#if defined(_MSC_VER)
_dupenv_s(&tzdir_env, nullptr, "TZDIR");
#else
tzdir_env = std::getenv("TZDIR");
#endif
if (tzdir_env && *tzdir_env) tzdir = tzdir_env;
path += tzdir;
path += '/';
#if defined(_MSC_VER)
free(tzdir_env);
#endif
}
path += name;
// Open the zoneinfo file.
FILE* fp = FOpen(path.c_str(), "rb");
if (fp == nullptr) return nullptr;
std::size_t length = 0;
if (fseek(fp, 0, SEEK_END) == 0) {
long pos = ftell(fp);
if (pos >= 0) {
length = static_cast<std::size_t>(pos);
}
rewind(fp);
}
return std::unique_ptr<ZoneInfoSource>(new FileZoneInfoSource(fp, length));
}
class AndroidZoneInfoSource : public FileZoneInfoSource {
public:
static std::unique_ptr<ZoneInfoSource> Open(const std::string& name);
std::string Version() const override { return version_; }
private:
explicit AndroidZoneInfoSource(FILE* fp, std::size_t len, const char* vers)
: FileZoneInfoSource(fp, len), version_(vers) {}
std::string version_;
};
std::unique_ptr<ZoneInfoSource> AndroidZoneInfoSource::Open(
const std::string& name) {
// Use of the "file:" prefix is intended for testing purposes only.
if (name.compare(0, 5, "file:") == 0) return Open(name.substr(5));
// See Android's libc/tzcode/bionic.cpp for additional information.
for (const char* tzdata : {"/data/misc/zoneinfo/current/tzdata",
"/system/usr/share/zoneinfo/tzdata"}) {
std::unique_ptr<FILE, int (*)(FILE*)> fp(FOpen(tzdata, "rb"), fclose);
if (fp.get() == nullptr) continue;
char hbuf[24]; // covers header.zonetab_offset too
if (fread(hbuf, 1, sizeof(hbuf), fp.get()) != sizeof(hbuf)) continue;
if (strncmp(hbuf, "tzdata", 6) != 0) continue;
const char* vers = (hbuf[11] == '\0') ? hbuf + 6 : "";
const std::int_fast32_t index_offset = Decode32(hbuf + 12);
const std::int_fast32_t data_offset = Decode32(hbuf + 16);
if (index_offset < 0 || data_offset < index_offset) continue;
if (fseek(fp.get(), static_cast<long>(index_offset), SEEK_SET) != 0)
continue;
char ebuf[52]; // covers entry.unused too
const std::size_t index_size =
static_cast<std::size_t>(data_offset - index_offset);
const std::size_t zonecnt = index_size / sizeof(ebuf);
if (zonecnt * sizeof(ebuf) != index_size) continue;
for (std::size_t i = 0; i != zonecnt; ++i) {
if (fread(ebuf, 1, sizeof(ebuf), fp.get()) != sizeof(ebuf)) break;
const std::int_fast32_t start = data_offset + Decode32(ebuf + 40);
const std::int_fast32_t length = Decode32(ebuf + 44);
if (start < 0 || length < 0) break;
ebuf[40] = '\0'; // ensure zone name is NUL terminated
if (strcmp(name.c_str(), ebuf) == 0) {
if (fseek(fp.get(), static_cast<long>(start), SEEK_SET) != 0) break;
return std::unique_ptr<ZoneInfoSource>(new AndroidZoneInfoSource(
fp.release(), static_cast<std::size_t>(length), vers));
}
}
}
Export of internal Abseil changes. -- 4a21ad4ffa957d28b770de8717289fab7410f567 by Gennadiy Rozental <rogeeff@google.com>: Internal cleanup PiperOrigin-RevId: 252366381 -- b6b0f25439549c54f1537a16625be1fecd3c7d8c by Xiaoyi Zhang <zhangxy@google.com>: Fix C4245 compiler warning of visual studio. This allows using abseil headers in code requiring strict warning settings. This is an import of https://github.com/abseil/abseil-cpp/pull/321. PiperOrigin-RevId: 252101240 -- 0543b7861b658a5a665298e1d868e29968ff7b27 by CJ Johnson <johnsoncj@google.com>: Adds new benchmarks for the constructors of InlinedVector PiperOrigin-RevId: 251905349 -- c65a08507917e9f8f6450b8beb235fe1426d7954 by CJ Johnson <johnsoncj@google.com>: Updates the InlinedVector BatchedBenchmark abstractions to 1) provide the index of the instance back to the prepare and test functions so that callers may perform extra work on local state with a unique per-instance ID and 2) reduce the number of manually written calls to BENCHMARK_TEMPLATE. PiperOrigin-RevId: 251895546 -- 99a1ae2d786b80096172f6e018711e15c0c750b9 by Samuel Benzaquen <sbenza@google.com>: Fix ambiguous construction problem in absl::variant<> to make in line with std::variant. ImaginaryFun is hiding duplicate objects instead of causing ambiguity. Add a second unique argument to make sure all overloads exist in the final overload set. PiperOrigin-RevId: 251860694 -- b54d0a12673be6ebb6e77e24a556ce9b758b3a7e by Abseil Team <absl-team@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 251739183 -- f51b115e0dc3fc9a9c9c20b33a1f27027a700d48 by Abseil Team <absl-team@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 251686812 -- 30e868049282dc6a6fc77d923ca7d2a5d35a1658 by Xiaoyi Zhang <zhangxy@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 251652119 GitOrigin-RevId: 4a21ad4ffa957d28b770de8717289fab7410f567 Change-Id: I7171cb613793fa90e0eb0143b65ec8264a2a84db
2019-06-10 10:57:44 +02:00
return nullptr;
}
} // namespace
bool TimeZoneInfo::Load(const std::string& name) {
// We can ensure that the loading of UTC or any other fixed-offset
// zone never fails because the simple, fixed-offset state can be
// internally generated. Note that this depends on our choice to not
// accept leap-second encoded ("right") zoneinfo.
auto offset = seconds::zero();
if (FixedOffsetFromName(name, &offset)) {
return ResetToBuiltinUTC(offset);
}
// Find and use a ZoneInfoSource to load the named zone.
auto zip = cctz_extension::zone_info_source_factory(
name, [](const std::string& name) -> std::unique_ptr<ZoneInfoSource> {
if (auto zip = FileZoneInfoSource::Open(name)) return zip;
if (auto zip = AndroidZoneInfoSource::Open(name)) return zip;
return nullptr;
});
return zip != nullptr && Load(name, zip.get());
}
// BreakTime() translation for a particular transition type.
time_zone::absolute_lookup TimeZoneInfo::LocalTime(
std::int_fast64_t unix_time, const TransitionType& tt) const {
// A civil time in "+offset" looks like (time+offset) in UTC.
// Note: We perform two additions in the civil_second domain to
// sidestep the chance of overflow in (unix_time + tt.utc_offset).
return {(civil_second() + unix_time) + tt.utc_offset,
tt.utc_offset, tt.is_dst, &abbreviations_[tt.abbr_index]};
}
// BreakTime() translation for a particular transition.
time_zone::absolute_lookup TimeZoneInfo::LocalTime(
std::int_fast64_t unix_time, const Transition& tr) const {
const TransitionType& tt = transition_types_[tr.type_index];
// Note: (unix_time - tr.unix_time) will never overflow as we
// have ensured that there is always a "nearby" transition.
return {tr.civil_sec + (unix_time - tr.unix_time), // TODO: Optimize.
tt.utc_offset, tt.is_dst, &abbreviations_[tt.abbr_index]};
}
// MakeTime() translation with a conversion-preserving +N * 400-year shift.
time_zone::civil_lookup TimeZoneInfo::TimeLocal(const civil_second& cs,
year_t c4_shift) const {
assert(last_year_ - 400 < cs.year() && cs.year() <= last_year_);
time_zone::civil_lookup cl = MakeTime(cs);
if (c4_shift > seconds::max().count() / kSecsPer400Years) {
cl.pre = cl.trans = cl.post = time_point<seconds>::max();
} else {
const auto offset = seconds(c4_shift * kSecsPer400Years);
const auto limit = time_point<seconds>::max() - offset;
for (auto* tp : {&cl.pre, &cl.trans, &cl.post}) {
if (*tp > limit) {
*tp = time_point<seconds>::max();
} else {
*tp += offset;
}
}
}
return cl;
}
time_zone::absolute_lookup TimeZoneInfo::BreakTime(
const time_point<seconds>& tp) const {
std::int_fast64_t unix_time = ToUnixSeconds(tp);
const std::size_t timecnt = transitions_.size();
assert(timecnt != 0); // We always add a transition.
if (unix_time < transitions_[0].unix_time) {
return LocalTime(unix_time, transition_types_[default_transition_type_]);
}
if (unix_time >= transitions_[timecnt - 1].unix_time) {
// After the last transition. If we extended the transitions using
// future_spec_, shift back to a supported year using the 400-year
// cycle of calendaric equivalence and then compensate accordingly.
if (extended_) {
const std::int_fast64_t diff =
unix_time - transitions_[timecnt - 1].unix_time;
const year_t shift = diff / kSecsPer400Years + 1;
const auto d = seconds(shift * kSecsPer400Years);
time_zone::absolute_lookup al = BreakTime(tp - d);
al.cs = YearShift(al.cs, shift * 400);
return al;
}
return LocalTime(unix_time, transitions_[timecnt - 1]);
}
const std::size_t hint = local_time_hint_.load(std::memory_order_relaxed);
if (0 < hint && hint < timecnt) {
if (transitions_[hint - 1].unix_time <= unix_time) {
if (unix_time < transitions_[hint].unix_time) {
return LocalTime(unix_time, transitions_[hint - 1]);
}
}
}
const Transition target = {unix_time, 0, civil_second(), civil_second()};
const Transition* begin = &transitions_[0];
const Transition* tr = std::upper_bound(begin, begin + timecnt, target,
Transition::ByUnixTime());
local_time_hint_.store(static_cast<std::size_t>(tr - begin),
std::memory_order_relaxed);
return LocalTime(unix_time, *--tr);
}
time_zone::civil_lookup TimeZoneInfo::MakeTime(const civil_second& cs) const {
const std::size_t timecnt = transitions_.size();
assert(timecnt != 0); // We always add a transition.
// Find the first transition after our target civil time.
const Transition* tr = nullptr;
const Transition* begin = &transitions_[0];
const Transition* end = begin + timecnt;
if (cs < begin->civil_sec) {
tr = begin;
} else if (cs >= transitions_[timecnt - 1].civil_sec) {
tr = end;
} else {
const std::size_t hint = time_local_hint_.load(std::memory_order_relaxed);
if (0 < hint && hint < timecnt) {
if (transitions_[hint - 1].civil_sec <= cs) {
if (cs < transitions_[hint].civil_sec) {
tr = begin + hint;
}
}
}
if (tr == nullptr) {
const Transition target = {0, 0, cs, civil_second()};
tr = std::upper_bound(begin, end, target, Transition::ByCivilTime());
time_local_hint_.store(static_cast<std::size_t>(tr - begin),
std::memory_order_relaxed);
}
}
if (tr == begin) {
if (tr->prev_civil_sec >= cs) {
// Before first transition, so use the default offset.
const TransitionType& tt(transition_types_[default_transition_type_]);
if (cs < tt.civil_min) return MakeUnique(time_point<seconds>::min());
return MakeUnique(cs - (civil_second() + tt.utc_offset));
}
// tr->prev_civil_sec < cs < tr->civil_sec
return MakeSkipped(*tr, cs);
}
if (tr == end) {
if (cs > (--tr)->prev_civil_sec) {
// After the last transition. If we extended the transitions using
// future_spec_, shift back to a supported year using the 400-year
// cycle of calendaric equivalence and then compensate accordingly.
if (extended_ && cs.year() > last_year_) {
const year_t shift = (cs.year() - last_year_ - 1) / 400 + 1;
return TimeLocal(YearShift(cs, shift * -400), shift);
}
const TransitionType& tt(transition_types_[tr->type_index]);
if (cs > tt.civil_max) return MakeUnique(time_point<seconds>::max());
return MakeUnique(tr->unix_time + (cs - tr->civil_sec));
}
// tr->civil_sec <= cs <= tr->prev_civil_sec
return MakeRepeated(*tr, cs);
}
if (tr->prev_civil_sec < cs) {
// tr->prev_civil_sec < cs < tr->civil_sec
return MakeSkipped(*tr, cs);
}
if (cs <= (--tr)->prev_civil_sec) {
// tr->civil_sec <= cs <= tr->prev_civil_sec
return MakeRepeated(*tr, cs);
}
// In between transitions.
return MakeUnique(tr->unix_time + (cs - tr->civil_sec));
}
std::string TimeZoneInfo::Version() const {
return version_;
}
std::string TimeZoneInfo::Description() const {
std::ostringstream oss;
oss << "#trans=" << transitions_.size();
oss << " #types=" << transition_types_.size();
oss << " spec='" << future_spec_ << "'";
return oss.str();
}
bool TimeZoneInfo::NextTransition(const time_point<seconds>& tp,
time_zone::civil_transition* trans) const {
if (transitions_.empty()) return false;
const Transition* begin = &transitions_[0];
const Transition* end = begin + transitions_.size();
if (begin->unix_time <= -(1LL << 59)) {
// Do not report the BIG_BANG found in recent zoneinfo data as it is
// really a sentinel, not a transition. See tz/zic.c.
++begin;
}
std::int_fast64_t unix_time = ToUnixSeconds(tp);
Export of internal Abseil changes. -- 8c420997e7a08b9e7e24afa32d6e37cb2bfa2c12 by Abseil Team <absl-team@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 231265916 -- f52b9e201698b315c23ebaa6c8ec97362048d9b8 by CJ Johnson <johnsoncj@google.com>: inlined_capacity() => GetInlinedCapacity() Originally I intended inlined_capacity() to be moved from the private to public part of the API eventually so I named it appropriately and punted on publicizing it until later. After it was found to cause an issue on some platforms, I decided there was no reason to grow the API of the type just for a pretty function name. Thus, this change brings its name to be of the same format as the rest of the Abseil team's naming convention. PiperOrigin-RevId: 231248856 -- 04e700ea1aad12cdb6a1ed29e183c59d97a47ccd by Abseil Team <absl-team@google.com>: Merge https://github.com/abseil/abseil-cpp/pull/255 PiperOrigin-RevId: 231240011 -- 4f2c2212c98093194c73572995e7770b58c9b9a0 by CJ Johnson <johnsoncj@google.com>: Adds identifiers to the AbslHashValue(...) forward declaration bringing it in line with the format of the other forward declarations while keeping it on one line. PiperOrigin-RevId: 231231932 -- 68923d6c9289eb523126638f25d95916456125cf by CJ Johnson <johnsoncj@google.com>: Remove bad calls to assert(...) that do not make sense PiperOrigin-RevId: 231214093 -- a2a0b59b7dc2c39aca979ff6e474b9e170ab96b6 by CJ Johnson <johnsoncj@google.com>: Switch to trailing return type syntax for non-member functions of InlinedVector PiperOrigin-RevId: 230975981 GitOrigin-RevId: 8c420997e7a08b9e7e24afa32d6e37cb2bfa2c12 Change-Id: Ibbad7f27b596801bc770b440afed8d5e9e89ff8d
2019-01-28 21:10:41 +01:00
const Transition target = {unix_time, 0, civil_second(), civil_second()};
const Transition* tr = std::upper_bound(begin, end, target,
Transition::ByUnixTime());
for (; tr != end; ++tr) { // skip no-op transitions
std::uint_fast8_t prev_type_index =
(tr == begin) ? default_transition_type_ : tr[-1].type_index;
if (!EquivTransitions(prev_type_index, tr[0].type_index)) break;
}
// When tr == end we return false, ignoring future_spec_.
if (tr == end) return false;
trans->from = tr->prev_civil_sec + 1;
trans->to = tr->civil_sec;
return true;
}
bool TimeZoneInfo::PrevTransition(const time_point<seconds>& tp,
time_zone::civil_transition* trans) const {
if (transitions_.empty()) return false;
const Transition* begin = &transitions_[0];
const Transition* end = begin + transitions_.size();
if (begin->unix_time <= -(1LL << 59)) {
// Do not report the BIG_BANG found in recent zoneinfo data as it is
// really a sentinel, not a transition. See tz/zic.c.
++begin;
}
std::int_fast64_t unix_time = ToUnixSeconds(tp);
if (FromUnixSeconds(unix_time) != tp) {
if (unix_time == std::numeric_limits<std::int_fast64_t>::max()) {
if (end == begin) return false; // Ignore future_spec_.
trans->from = (--end)->prev_civil_sec + 1;
trans->to = end->civil_sec;
return true;
}
unix_time += 1; // ceils
}
Export of internal Abseil changes. -- 8c420997e7a08b9e7e24afa32d6e37cb2bfa2c12 by Abseil Team <absl-team@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 231265916 -- f52b9e201698b315c23ebaa6c8ec97362048d9b8 by CJ Johnson <johnsoncj@google.com>: inlined_capacity() => GetInlinedCapacity() Originally I intended inlined_capacity() to be moved from the private to public part of the API eventually so I named it appropriately and punted on publicizing it until later. After it was found to cause an issue on some platforms, I decided there was no reason to grow the API of the type just for a pretty function name. Thus, this change brings its name to be of the same format as the rest of the Abseil team's naming convention. PiperOrigin-RevId: 231248856 -- 04e700ea1aad12cdb6a1ed29e183c59d97a47ccd by Abseil Team <absl-team@google.com>: Merge https://github.com/abseil/abseil-cpp/pull/255 PiperOrigin-RevId: 231240011 -- 4f2c2212c98093194c73572995e7770b58c9b9a0 by CJ Johnson <johnsoncj@google.com>: Adds identifiers to the AbslHashValue(...) forward declaration bringing it in line with the format of the other forward declarations while keeping it on one line. PiperOrigin-RevId: 231231932 -- 68923d6c9289eb523126638f25d95916456125cf by CJ Johnson <johnsoncj@google.com>: Remove bad calls to assert(...) that do not make sense PiperOrigin-RevId: 231214093 -- a2a0b59b7dc2c39aca979ff6e474b9e170ab96b6 by CJ Johnson <johnsoncj@google.com>: Switch to trailing return type syntax for non-member functions of InlinedVector PiperOrigin-RevId: 230975981 GitOrigin-RevId: 8c420997e7a08b9e7e24afa32d6e37cb2bfa2c12 Change-Id: Ibbad7f27b596801bc770b440afed8d5e9e89ff8d
2019-01-28 21:10:41 +01:00
const Transition target = {unix_time, 0, civil_second(), civil_second()};
const Transition* tr = std::lower_bound(begin, end, target,
Transition::ByUnixTime());
for (; tr != begin; --tr) { // skip no-op transitions
std::uint_fast8_t prev_type_index =
(tr - 1 == begin) ? default_transition_type_ : tr[-2].type_index;
if (!EquivTransitions(prev_type_index, tr[-1].type_index)) break;
}
// When tr == end we return the "last" transition, ignoring future_spec_.
if (tr == begin) return false;
trans->from = (--tr)->prev_civil_sec + 1;
trans->to = tr->civil_sec;
return true;
}
} // namespace cctz
} // namespace time_internal
} // namespace absl