74d91756c1
-- a874475e842d2adeb31bb7bd37bdd6eb15a2aeb9 by Mark Barolak <mbar@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 256414250 -- c95e6c21944c59c9b9b9e7eb9dc79cfb9ae5ef8d by CJ Johnson <johnsoncj@google.com>: Update the license year + run clang-format for the FixedArray and InlinedVector test files PiperOrigin-RevId: 256376285 -- f430b04f332d6b89cb8447b07217e391e1c38000 by Derek Mauro <dmauro@google.com>: Migrate the Linux CMake tests from GCC 4.8 to the GCC latest version. This will allow us to delete the GCC 4.8 test since that is currently our only CMake coverage. This also means that we don't have to update the script every time we move to a new minumum GCC version. This change includes a fix for a -Wstringops-truncation warning in symbolize_test.cc that triggers when it is built in release mode with the latest GCC. PiperOrigin-RevId: 256370092 GitOrigin-RevId: a874475e842d2adeb31bb7bd37bdd6eb15a2aeb9 Change-Id: Ia2ec58f9b9dfc382d043344e346cb397b802270a
975 lines
37 KiB
C++
975 lines
37 KiB
C++
// 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
|
|
//
|
|
// 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
|
|
// 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_ttisutcnt)) < 0) return false;
|
|
ttisutcnt = 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 * ttisutcnt; // 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.ttisutcnt != 0 && hdr.ttisutcnt != 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.ttisutcnt; // 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* azip) -> int {
|
|
unsigned char ch; // all non-EOF results are positive
|
|
return (azip->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));
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
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
|
|
}
|
|
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
|