| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 |
| * |
| * http://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. |
| */ |
| |
| #include "orc/OrcFile.hh" |
| #include "Timezone.hh" |
| |
| #include <errno.h> |
| #include <map> |
| #include <sstream> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| |
| namespace orc { |
| |
| // default location of the timezone files |
| static const char DEFAULT_TZDIR[] = "/usr/share/zoneinfo"; |
| |
| // location of a symlink to the local timezone |
| static const char LOCAL_TIMEZONE[] = "/etc/localtime"; |
| |
| enum TransitionKind { |
| TRANSITION_JULIAN, |
| TRANSITION_DAY, |
| TRANSITION_MONTH |
| }; |
| |
| static const int64_t MONTHS_PER_YEAR = 12; |
| /** |
| * The number of days in each month in non-leap and leap years. |
| */ |
| static const int64_t DAYS_PER_MONTH[2][MONTHS_PER_YEAR] = |
| {{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, |
| {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}; |
| static const int64_t DAYS_PER_WEEK = 7; |
| |
| // Leap years and day of the week repeat every 400 years, which makes it |
| // a good cycle length. |
| static const int64_t SECONDS_PER_400_YEARS = |
| SECONDS_PER_DAY * (365 * (300 + 3) + 366 * (100 - 3)); |
| |
| /** |
| * Is the given year a leap year? |
| */ |
| bool isLeap(int64_t year) { |
| return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)); |
| } |
| |
| /** |
| * Find the position that is the closest and less than or equal to the |
| * target. |
| * @return -1 if the target < array[0] or array is empty or |
| * i if array[i] <= target and (i == n or array[i] < array[i+1]) |
| */ |
| int64_t binarySearch(const std::vector<int64_t> &array, int64_t target) { |
| uint64_t size = array.size(); |
| if (size == 0) { |
| return -1; |
| } |
| uint64_t min = 0; |
| uint64_t max = size - 1; |
| uint64_t mid = (min + max) / 2; |
| while ((array[mid] != target) && (min < max)) { |
| if (array[mid] < target) { |
| min = mid + 1; |
| } else if (mid == 0) { |
| max = 0; |
| } else { |
| max = mid - 1; |
| } |
| mid = (min + max) / 2; |
| } |
| if (target < array[mid]) { |
| return static_cast<int64_t>(mid) - 1; |
| } else { |
| return static_cast<int64_t>(mid); |
| } |
| } |
| |
| struct Transition { |
| TransitionKind kind; |
| int64_t day; |
| int64_t week; |
| int64_t month; |
| int64_t time; |
| |
| std::string toString() const { |
| std::stringstream buffer; |
| switch (kind) { |
| case TRANSITION_JULIAN: |
| buffer << "julian " << day; |
| break; |
| case TRANSITION_DAY: |
| buffer << "day " << day; |
| break; |
| case TRANSITION_MONTH: |
| buffer << "month " << month << " week " << week << " day " << day; |
| break; |
| } |
| buffer << " at " << (time / (60 * 60)) << ":" << ((time / 60) % 60) |
| << ":" << (time % 60); |
| return buffer.str(); |
| } |
| |
| /** |
| * Get the transition time for the given year. |
| * @param year the year |
| * @return the number of seconds past local Jan 1 00:00:00 that the |
| * transition happens. |
| */ |
| int64_t getTime(int64_t year) const { |
| int64_t result = time; |
| switch (kind) { |
| case TRANSITION_JULIAN: |
| result += SECONDS_PER_DAY * day; |
| if (day > 60 && isLeap(year)) { |
| result += SECONDS_PER_DAY; |
| } |
| break; |
| case TRANSITION_DAY: |
| result += SECONDS_PER_DAY * day; |
| break; |
| case TRANSITION_MONTH: { |
| bool inLeap = isLeap(year); |
| int64_t adjustedMonth = (month + 9) % 12 + 1; |
| int64_t adjustedYear = (month <= 2) ? (year - 1) : year; |
| int64_t adjustedCentury = adjustedYear / 100; |
| int64_t adjustedRemainder = adjustedYear % 100; |
| |
| // day of the week of the first day of month |
| int64_t dayOfWeek = ((26 * adjustedMonth - 2) / 10 + |
| 1 + adjustedRemainder + adjustedRemainder / 4 + |
| adjustedCentury / 4 - 2 * adjustedCentury) % 7; |
| if (dayOfWeek < 0) { |
| dayOfWeek += DAYS_PER_WEEK; |
| } |
| |
| int64_t d = day - dayOfWeek; |
| if (d < 0) { |
| d += DAYS_PER_WEEK; |
| } |
| for (int w = 1; w < week; ++w) { |
| if (d + DAYS_PER_WEEK >= DAYS_PER_MONTH[inLeap][month - 1]) { |
| break; |
| } |
| d += DAYS_PER_WEEK; |
| } |
| result += d * SECONDS_PER_DAY; |
| |
| // Add in the time for the month |
| for(int m=0; m < month - 1; ++m) { |
| result += DAYS_PER_MONTH[inLeap][m] * SECONDS_PER_DAY; |
| } |
| break; |
| } |
| } |
| return result; |
| } |
| }; |
| |
| /** |
| * The current rule for finding timezone variants arbitrarily far in |
| * the future. They are based on a string representation that |
| * specifies the standard name and offset. For timezones with |
| * daylight savings, the string specifies the daylight variant name |
| * and offset and the rules for switching between them. |
| * |
| * rule = <standard name><standard offset><daylight>? |
| * name = string with no numbers or '+', '-', or ',' |
| * offset = [-+]?hh(:mm(:ss)?)? |
| * daylight = <name><offset>,<start day>(/<offset>)?,<end day>(/<offset>)? |
| * day = J<day without 2/29>|<day with 2/29>|M<month>.<week>.<day of week> |
| */ |
| class FutureRuleImpl: public FutureRule { |
| std::string ruleString; |
| TimezoneVariant standard; |
| bool hasDst; |
| TimezoneVariant dst; |
| Transition start; |
| Transition end; |
| |
| // expanded time_t offsets of transitions |
| std::vector<int64_t> offsets; |
| |
| // Is the epoch (1 Jan 1970 00:00) in standard time? |
| // This code assumes that the transition dates fall in the same order |
| // each year. Hopefully no timezone regions decide to move across the |
| // equator, which is about what it would take. |
| bool startInStd; |
| |
| void computeOffsets() { |
| if (!hasDst) { |
| startInStd = true; |
| offsets.resize(1); |
| } else { |
| // Insert a transition for the epoch and two per a year for the next |
| // 400 years. We assume that the all even positions are in standard |
| // time if and only if startInStd and the odd ones are the reverse. |
| offsets.resize(400 * 2 + 1); |
| startInStd = start.getTime(1970) < end.getTime(1970); |
| int64_t base = 0; |
| for(int64_t year = 1970; year < 1970 + 400; ++year) { |
| if (startInStd) { |
| offsets[static_cast<uint64_t>(year - 1970) * 2 + 1] = |
| base + start.getTime(year) - standard.gmtOffset; |
| offsets[static_cast<uint64_t>(year - 1970) * 2 + 2] = |
| base + end.getTime(year) - dst.gmtOffset; |
| } else { |
| offsets[static_cast<uint64_t>(year - 1970) * 2 + 1] = |
| base + end.getTime(year) - dst.gmtOffset; |
| offsets[static_cast<uint64_t>(year - 1970) * 2 + 2] = |
| base + start.getTime(year) - standard.gmtOffset; |
| } |
| base += (isLeap(year) ? 366 : 365) * SECONDS_PER_DAY; |
| } |
| } |
| offsets[0] = 0; |
| } |
| |
| public: |
| virtual ~FutureRuleImpl() override; |
| bool isDefined() const override; |
| const TimezoneVariant& getVariant(int64_t clk) const override; |
| void print(std::ostream& out) const override; |
| |
| friend class FutureRuleParser; |
| }; |
| |
| FutureRule::~FutureRule() { |
| // PASS |
| } |
| |
| FutureRuleImpl::~FutureRuleImpl() { |
| // PASS |
| } |
| |
| bool FutureRuleImpl::isDefined() const { |
| return ruleString.size() > 0; |
| } |
| |
| const TimezoneVariant& FutureRuleImpl::getVariant(int64_t clk) const { |
| if (!hasDst) { |
| return standard; |
| } else { |
| int64_t adjusted = clk % SECONDS_PER_400_YEARS; |
| if (adjusted < 0) { |
| adjusted += SECONDS_PER_400_YEARS; |
| } |
| int64_t idx = binarySearch(offsets, adjusted); |
| if (startInStd == (idx % 2 == 0)) { |
| return standard; |
| } else { |
| return dst; |
| } |
| } |
| } |
| |
| void FutureRuleImpl::print(std::ostream& out) const { |
| if (isDefined()) { |
| out << " Future rule: " << ruleString << "\n"; |
| out << " standard " << standard.toString() << "\n"; |
| if (hasDst) { |
| out << " dst " << dst.toString() << "\n"; |
| out << " start " << start.toString() << "\n"; |
| out << " end " << end.toString() << "\n"; |
| } |
| } |
| } |
| |
| /** |
| * A parser for the future rule strings. |
| */ |
| class FutureRuleParser { |
| public: |
| FutureRuleParser(const std::string& str, |
| FutureRuleImpl* rule |
| ): ruleString(str), |
| length(str.size()), |
| position(0), |
| output(*rule) { |
| output.ruleString = str; |
| if (position != length) { |
| parseName(output.standard.name); |
| output.standard.gmtOffset = -parseOffset(); |
| output.standard.isDst = false; |
| output.hasDst = position < length; |
| if (output.hasDst) { |
| parseName(output.dst.name); |
| output.dst.isDst = true; |
| if (ruleString[position] != ',') { |
| output.dst.gmtOffset = -parseOffset(); |
| } else { |
| output.dst.gmtOffset = output.standard.gmtOffset + 60 * 60; |
| } |
| parseTransition(output.start); |
| parseTransition(output.end); |
| } |
| if (position != length) { |
| throwError("Extra text"); |
| } |
| output.computeOffsets(); |
| } |
| } |
| |
| private: |
| |
| const std::string& ruleString; |
| size_t length; |
| size_t position; |
| FutureRuleImpl &output; |
| |
| void throwError(const char *msg) { |
| std::stringstream buffer; |
| buffer << msg << " at " << position << " in '" << ruleString << "'"; |
| throw TimezoneError(buffer.str()); |
| } |
| |
| /** |
| * Parse the names of the form: |
| * ([^-+0-9,]+|<[^>]+>) |
| * and set the output string. |
| */ |
| void parseName(std::string& result) { |
| if (position == length) { |
| throwError("name required"); |
| } |
| size_t start = position; |
| if (ruleString[position] == '<') { |
| while (position < length && ruleString[position] != '>') { |
| position += 1; |
| } |
| if (position == length) { |
| throwError("missing close '>'"); |
| } |
| position +=1; |
| } else { |
| while (position < length) { |
| char ch = ruleString[position]; |
| if (isdigit(ch) || ch == '-' || ch == '+' || ch == ',') { |
| break; |
| } |
| position += 1; |
| } |
| } |
| if (position == start) { |
| throwError("empty string not allowed"); |
| } |
| result = ruleString.substr(start, position - start); |
| } |
| |
| /** |
| * Parse an integer of the form [0-9]+ and return it. |
| */ |
| int64_t parseNumber() { |
| if (position >= length) { |
| throwError("missing number"); |
| } |
| int64_t result = 0; |
| while (position < length) { |
| char ch = ruleString[position]; |
| if (isdigit(ch)) { |
| result = result * 10 + (ch - '0'); |
| position += 1; |
| } else { |
| break; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Parse the offsets of the form: |
| * [-+]?[0-9]+(:[0-9]+(:[0-9]+)?)? |
| * and convert it into a number of seconds. |
| */ |
| int64_t parseOffset() { |
| int64_t scale = 3600; |
| bool isNegative = false; |
| if (position < length) { |
| char ch = ruleString[position]; |
| isNegative = ch == '-'; |
| if (ch == '-' || ch == '+') { |
| position += 1; |
| } |
| } |
| int64_t result = parseNumber() * scale; |
| while (position < length && scale > 1 && ruleString[position] == ':') { |
| scale /= 60; |
| position += 1; |
| result += parseNumber() * scale; |
| } |
| if (isNegative) { |
| result = -result; |
| } |
| return result; |
| } |
| |
| /** |
| * Parse a transition of the following form: |
| * ,(J<number>|<number>|M<number>.<number>.<number>)(/<offset>)? |
| */ |
| void parseTransition(Transition& transition) { |
| if (length - position < 2 || ruleString[position] != ',') { |
| throwError("missing transition"); |
| } |
| position += 1; |
| char ch = ruleString[position]; |
| if (ch == 'J') { |
| transition.kind = TRANSITION_JULIAN; |
| position += 1; |
| transition.day = parseNumber(); |
| } else if (ch == 'M') { |
| transition.kind = TRANSITION_MONTH; |
| position += 1; |
| transition.month = parseNumber(); |
| if (position == length || ruleString[position] != '.') { |
| throwError("missing first ."); |
| } |
| position += 1; |
| transition.week = parseNumber(); |
| if (position == length || ruleString[position] != '.') { |
| throwError("missing second ."); |
| } |
| position += 1; |
| transition.day = parseNumber(); |
| } else { |
| transition.kind = TRANSITION_DAY; |
| transition.day = parseNumber(); |
| } |
| if (position < length && ruleString[position] == '/') { |
| position += 1; |
| transition.time = parseOffset(); |
| } else { |
| transition.time = 2 * 60 * 60; |
| } |
| } |
| }; |
| |
| /** |
| * Parse the POSIX TZ string. |
| */ |
| std::shared_ptr<FutureRule> parseFutureRule(const std::string& ruleString) { |
| std::shared_ptr<FutureRule> result(new FutureRuleImpl()); |
| FutureRuleParser parser(ruleString, |
| dynamic_cast<FutureRuleImpl*>(result.get())); |
| return result; |
| } |
| |
| std::string TimezoneVariant::toString() const { |
| std::stringstream buffer; |
| buffer << name << " " << gmtOffset; |
| if (isDst) { |
| buffer << " (dst)"; |
| } |
| return buffer.str(); |
| } |
| |
| /** |
| * An abstraction of the differences between versions. |
| */ |
| class VersionParser { |
| public: |
| virtual ~VersionParser(); |
| |
| /** |
| * Get the version number. |
| */ |
| virtual uint64_t getVersion() const = 0; |
| |
| /** |
| * Get the number of bytes |
| */ |
| virtual uint64_t getTimeSize() const = 0; |
| |
| /** |
| * Parse the time at the given location. |
| */ |
| virtual int64_t parseTime(const unsigned char* ptr) const = 0; |
| |
| /** |
| * Parse the future string |
| */ |
| virtual std::string parseFutureString(const unsigned char *ptr, |
| uint64_t offset, |
| uint64_t length) const = 0; |
| }; |
| |
| VersionParser::~VersionParser() { |
| // PASS |
| } |
| |
| static uint32_t decode32(const unsigned char* ptr) { |
| return static_cast<uint32_t>(ptr[0] << 24) | |
| static_cast<uint32_t>(ptr[1] << 16) | |
| static_cast<uint32_t>(ptr[2] << 8) | |
| static_cast<uint32_t>(ptr[3]); |
| } |
| |
| class Version1Parser: public VersionParser { |
| public: |
| virtual ~Version1Parser() override; |
| |
| virtual uint64_t getVersion() const override { |
| return 1; |
| } |
| |
| /** |
| * Get the number of bytes |
| */ |
| virtual uint64_t getTimeSize() const override { |
| return 4; |
| } |
| |
| /** |
| * Parse the time at the given location. |
| */ |
| virtual int64_t parseTime(const unsigned char* ptr) const override { |
| // sign extend from 32 bits |
| return static_cast<int32_t>(decode32(ptr)); |
| } |
| |
| virtual std::string parseFutureString(const unsigned char *, |
| uint64_t, |
| uint64_t) const override { |
| return ""; |
| } |
| }; |
| |
| Version1Parser::~Version1Parser() { |
| // PASS |
| } |
| |
| class Version2Parser: public VersionParser { |
| public: |
| virtual ~Version2Parser() override; |
| |
| virtual uint64_t getVersion() const override { |
| return 2; |
| } |
| |
| /** |
| * Get the number of bytes |
| */ |
| virtual uint64_t getTimeSize() const override { |
| return 8; |
| } |
| |
| /** |
| * Parse the time at the given location. |
| */ |
| virtual int64_t parseTime(const unsigned char* ptr) const override { |
| return static_cast<int64_t>(decode32(ptr)) << 32 | decode32(ptr + 4); |
| } |
| |
| virtual std::string parseFutureString(const unsigned char *ptr, |
| uint64_t offset, |
| uint64_t length) const override { |
| return std::string(reinterpret_cast<const char*>(ptr) + offset + 1, |
| length - 2); |
| } |
| }; |
| |
| Version2Parser::~Version2Parser() { |
| // PASS |
| } |
| |
| class TimezoneImpl: public Timezone { |
| public: |
| TimezoneImpl(const std::string& name, |
| const std::vector<unsigned char> bytes); |
| virtual ~TimezoneImpl() override; |
| |
| /** |
| * Get the variant for the given time (time_t). |
| */ |
| const TimezoneVariant& getVariant(int64_t clk) const override; |
| |
| void print(std::ostream&) const override; |
| |
| uint64_t getVersion() const override { |
| return version; |
| } |
| |
| int64_t getEpoch() const override { |
| return epoch; |
| } |
| |
| int64_t convertToUTC(int64_t clk) const override { |
| return clk + getVariant(clk).gmtOffset; |
| } |
| |
| private: |
| void parseTimeVariants(const unsigned char* ptr, |
| uint64_t variantOffset, |
| uint64_t variantCount, |
| uint64_t nameOffset, |
| uint64_t nameCount); |
| void parseZoneFile(const unsigned char* ptr, |
| uint64_t sectionOffset, |
| uint64_t fileLength, |
| const VersionParser& version); |
| // filename |
| std::string filename; |
| |
| // the version of the file |
| uint64_t version; |
| |
| // the list of variants for this timezone |
| std::vector<TimezoneVariant> variants; |
| |
| // the list of the times where the local rules change |
| std::vector<int64_t> transitions; |
| |
| // the variant that starts at this transition. |
| std::vector<uint64_t> currentVariant; |
| |
| // the variant before the first transition |
| uint64_t ancientVariant; |
| |
| // the rule for future times |
| std::shared_ptr<FutureRule> futureRule; |
| |
| // the last explicit transition after which we use the future rule |
| int64_t lastTransition; |
| |
| // The ORC epoch time in this timezone. |
| int64_t epoch; |
| }; |
| |
| DIAGNOSTIC_PUSH |
| #ifdef __clang__ |
| DIAGNOSTIC_IGNORE("-Wglobal-constructors") |
| DIAGNOSTIC_IGNORE("-Wexit-time-destructors") |
| #endif |
| static std::mutex timezone_mutex; |
| static std::map<std::string, std::shared_ptr<Timezone> > timezoneCache; |
| DIAGNOSTIC_POP |
| |
| Timezone::~Timezone() { |
| // PASS |
| } |
| |
| TimezoneImpl::TimezoneImpl(const std::string& _filename, |
| const std::vector<unsigned char> buffer |
| ): filename(_filename) { |
| parseZoneFile(&buffer[0], 0, buffer.size(), Version1Parser()); |
| // Build the literal for the ORC epoch |
| // 2015 Jan 1 00:00:00 |
| tm epochStruct; |
| epochStruct.tm_sec = 0; |
| epochStruct.tm_min = 0; |
| epochStruct.tm_hour = 0; |
| epochStruct.tm_mday = 1; |
| epochStruct.tm_mon = 0; |
| epochStruct.tm_year = 2015 - 1900; |
| epochStruct.tm_isdst = 0; |
| time_t utcEpoch = timegm(&epochStruct); |
| epoch = utcEpoch - getVariant(utcEpoch).gmtOffset; |
| } |
| |
| const char* getTimezoneDirectory() { |
| const char *dir = getenv("TZDIR"); |
| if (!dir) { |
| dir = DEFAULT_TZDIR; |
| } |
| return dir; |
| } |
| |
| /** |
| * Get a timezone by absolute filename. |
| * Results are cached. |
| */ |
| const Timezone& getTimezoneByFilename(const std::string& filename) { |
| // ORC-110 |
| std::lock_guard<std::mutex> timezone_lock(timezone_mutex); |
| std::map<std::string, std::shared_ptr<Timezone> >::iterator itr = |
| timezoneCache.find(filename); |
| if (itr != timezoneCache.end()) { |
| return *(itr->second).get(); |
| } |
| try { |
| ORC_UNIQUE_PTR<InputStream> file = readFile(filename); |
| size_t size = static_cast<size_t>(file->getLength()); |
| std::vector<unsigned char> buffer(size); |
| file->read(&buffer[0], size, 0); |
| timezoneCache[filename] = std::shared_ptr<Timezone>(new TimezoneImpl(filename, buffer)); |
| } catch(ParseError& err) { |
| throw TimezoneError(err.what()); |
| } |
| return *timezoneCache[filename].get(); |
| } |
| |
| /** |
| * Get the local timezone. |
| */ |
| const Timezone& getLocalTimezone() { |
| #ifdef _MSC_VER |
| return getTimezoneByName("UTC"); |
| #else |
| return getTimezoneByFilename(LOCAL_TIMEZONE); |
| #endif |
| } |
| |
| /** |
| * Get a timezone by name (eg. America/Los_Angeles). |
| * Results are cached. |
| */ |
| const Timezone& getTimezoneByName(const std::string& zone) { |
| std::string filename(getTimezoneDirectory()); |
| filename += "/"; |
| filename += zone; |
| return getTimezoneByFilename(filename); |
| } |
| |
| /** |
| * Parse a set of bytes as a timezone file as if they came from filename. |
| */ |
| std::unique_ptr<Timezone> getTimezone(const std::string& filename, |
| const std::vector<unsigned char>& b){ |
| return std::unique_ptr<Timezone>(new TimezoneImpl(filename, b)); |
| } |
| |
| TimezoneImpl::~TimezoneImpl() { |
| // PASS |
| } |
| |
| void TimezoneImpl::parseTimeVariants(const unsigned char* ptr, |
| uint64_t variantOffset, |
| uint64_t variantCount, |
| uint64_t nameOffset, |
| uint64_t nameCount) { |
| for(uint64_t variant=0; variant < variantCount; ++variant) { |
| variants[variant].gmtOffset = |
| static_cast<int32_t>(decode32(ptr + variantOffset + 6 * variant)); |
| variants[variant].isDst = ptr[variantOffset + 6 * variant + 4] != 0; |
| uint64_t nameStart = ptr[variantOffset + 6 * variant + 5]; |
| if (nameStart >= nameCount) { |
| std::stringstream buffer; |
| buffer << "name out of range in variant " << variant |
| << " - " << nameStart << " >= " << nameCount; |
| throw TimezoneError(buffer.str()); |
| } |
| variants[variant].name = std::string(reinterpret_cast<const char*>(ptr) |
| + nameOffset + nameStart); |
| } |
| } |
| |
| /** |
| * Parse the zone file to get the bits we need. |
| * There are two versions of the timezone file: |
| * |
| * Version 1(version = 0x00): |
| * Magic(version) |
| * Header |
| * TransitionTimes(4 byte) |
| * TransitionRules |
| * Rules |
| * LeapSeconds(4 byte) |
| * IsStd |
| * IsGmt |
| * |
| * Version2: |
| * Version1(0x32) = a version 1 copy of the data for old clients |
| * Magic(0x32) |
| * Header |
| * TransitionTimes(8 byte) |
| * TransitionRules |
| * Rules |
| * LeapSeconds(8 byte) |
| * IsStd |
| * IsGmt |
| * FutureString |
| */ |
| void TimezoneImpl::parseZoneFile(const unsigned char *ptr, |
| uint64_t sectionOffset, |
| uint64_t fileLength, |
| const VersionParser& versionParser) { |
| const uint64_t magicOffset = sectionOffset + 0; |
| const uint64_t headerOffset = magicOffset + 20; |
| |
| // check for validity before we start parsing |
| if (fileLength < headerOffset + 6 * 4 || |
| strncmp(reinterpret_cast<const char*>(ptr) + magicOffset, "TZif", 4) |
| != 0) { |
| std::stringstream buffer; |
| buffer << "non-tzfile " << filename; |
| throw TimezoneError(buffer.str()); |
| } |
| |
| const uint64_t isGmtCount = decode32(ptr + headerOffset + 0); |
| const uint64_t isStdCount = decode32(ptr + headerOffset + 4); |
| const uint64_t leapCount = decode32(ptr + headerOffset + 8); |
| const uint64_t timeCount = decode32(ptr + headerOffset + 12); |
| const uint64_t variantCount = decode32(ptr + headerOffset + 16); |
| const uint64_t nameCount = decode32(ptr + headerOffset + 20); |
| |
| const uint64_t timeOffset = headerOffset + 24; |
| const uint64_t timeVariantOffset = |
| timeOffset + versionParser.getTimeSize() * timeCount; |
| const uint64_t variantOffset = timeVariantOffset + timeCount; |
| const uint64_t nameOffset = variantOffset + variantCount * 6; |
| const uint64_t sectionLength = nameOffset + nameCount |
| + (versionParser.getTimeSize() + 4) * leapCount |
| + isGmtCount + isStdCount; |
| |
| if (sectionLength > fileLength) { |
| std::stringstream buffer; |
| buffer << "tzfile too short " << filename |
| << " needs " << sectionLength << " and has " << fileLength; |
| throw TimezoneError(buffer.str()); |
| } |
| |
| // if it is version 2, skip over the old layout and read the new one. |
| if (sectionOffset == 0 && ptr[magicOffset + 4] != 0) { |
| parseZoneFile(ptr, sectionLength, fileLength, Version2Parser()); |
| return; |
| } |
| version = versionParser.getVersion(); |
| variants.resize(variantCount); |
| transitions.resize(timeCount); |
| currentVariant.resize(timeCount); |
| parseTimeVariants(ptr, variantOffset, variantCount, nameOffset, |
| nameCount); |
| bool foundAncient = false; |
| for(uint64_t t=0; t < timeCount; ++t) { |
| transitions[t] = |
| versionParser.parseTime(ptr + timeOffset + |
| t * versionParser.getTimeSize()); |
| currentVariant[t] = ptr[timeVariantOffset + t]; |
| if (currentVariant[t] >= variantCount) { |
| std::stringstream buffer; |
| buffer << "tzfile rule out of range " << filename |
| << " references rule " << currentVariant[t] |
| << " of " << variantCount; |
| throw TimezoneError(buffer.str()); |
| } |
| // find the oldest standard time and use that as the ancient value |
| if (!foundAncient && |
| !variants[currentVariant[t]].isDst) { |
| foundAncient = true; |
| ancientVariant = currentVariant[t]; |
| } |
| } |
| if (!foundAncient) { |
| ancientVariant = 0; |
| } |
| futureRule = parseFutureRule(versionParser.parseFutureString |
| (ptr, sectionLength, |
| fileLength - sectionLength)); |
| |
| // find the lower bound for applying the future rule |
| if (futureRule->isDefined()) { |
| if (timeCount > 0) { |
| lastTransition = transitions[timeCount - 1]; |
| } else { |
| lastTransition = INT64_MIN; |
| } |
| } else { |
| lastTransition = INT64_MAX; |
| } |
| } |
| |
| const TimezoneVariant& TimezoneImpl::getVariant(int64_t clk) const { |
| // if it is after the last explicit entry in the table, |
| // use the future rule to get an answer |
| if (clk > lastTransition) { |
| return futureRule->getVariant(clk); |
| } else { |
| int64_t transition = binarySearch(transitions, clk); |
| uint64_t idx; |
| if (transition < 0) { |
| idx = ancientVariant; |
| } else { |
| idx = currentVariant[static_cast<size_t>(transition)]; |
| } |
| return variants[idx]; |
| } |
| } |
| |
| void TimezoneImpl::print(std::ostream& out) const { |
| out << "Timezone file: " << filename << "\n"; |
| out << " Version: " << version << "\n"; |
| futureRule->print(out); |
| for(uint64_t r=0; r < variants.size(); ++r) { |
| out << " Variant " << r << ": " |
| << variants[r].toString() << "\n"; |
| } |
| for(uint64_t t=0; t < transitions.size(); ++t) { |
| tm timeStruct; |
| tm* result = nullptr; |
| char buffer[25]; |
| if (sizeof(time_t) >= 8) { |
| time_t val = transitions[t]; |
| result = gmtime_r(&val, &timeStruct); |
| if (result) { |
| strftime(buffer, sizeof(buffer), "%F %H:%M:%S", &timeStruct); |
| } |
| } |
| std::cout << " Transition: " << (result == nullptr ? "null" : buffer) |
| << " (" << transitions[t] << ") -> " |
| << variants[currentVariant[t]].name |
| << "\n"; |
| } |
| } |
| |
| TimezoneError::TimezoneError(const std::string& what |
| ): std::runtime_error(what) { |
| // PASS |
| } |
| |
| TimezoneError::TimezoneError(const TimezoneError& other |
| ): std::runtime_error(other) { |
| // PASS |
| } |
| |
| TimezoneError::~TimezoneError() ORC_NOEXCEPT { |
| // PASS |
| } |
| |
| } |