Coverage Report

Created: 2026-03-12 17:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/util/timezone_utils.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#include "util/timezone_utils.h"
19
20
#include <cctz/civil_time.h>
21
#include <cctz/time_zone.h>
22
#include <fcntl.h>
23
#include <glog/logging.h>
24
#include <re2/re2.h>
25
#include <re2/stringpiece.h>
26
#include <sys/mman.h>
27
#include <sys/stat.h>
28
#include <sys/types.h>
29
#include <unistd.h>
30
31
#include <boost/algorithm/string.hpp>
32
#include <boost/algorithm/string/case_conv.hpp>
33
#include <cstdlib>
34
#include <filesystem>
35
#include <memory>
36
#include <string>
37
38
#include "common/exception.h"
39
#include "common/logging.h"
40
#include "common/status.h"
41
42
using boost::algorithm::to_lower_copy;
43
44
namespace fs = std::filesystem;
45
#include "common/compile_check_begin.h"
46
47
namespace doris {
48
49
using ZoneList = std::unordered_map<std::string, cctz::time_zone>;
50
51
RE2 time_zone_offset_format_reg(R"(^[+-]{1}\d{2}\:\d{2}$)"); // visiting is thread-safe
52
53
// for ut, make it never nullptr.
54
std::unique_ptr<ZoneList> lower_zone_cache_ = std::make_unique<ZoneList>();
55
56
const std::string TimezoneUtils::default_time_zone = "+08:00";
57
static const char* tzdir = "/usr/share/zoneinfo"; // default value, may change by TZDIR env var
58
59
6
void TimezoneUtils::clear_timezone_caches() {
60
6
    lower_zone_cache_->clear();
61
6
}
62
2
size_t TimezoneUtils::cache_size() {
63
2
    return lower_zone_cache_->size();
64
2
}
65
66
146k
static bool parse_save_name_tz(const std::string& tz_name) {
67
146k
    cctz::time_zone tz;
68
146k
    PROPAGATE_FALSE(cctz::load_time_zone(tz_name, &tz));
69
145k
    lower_zone_cache_->emplace(to_lower_copy(tz_name), tz);
70
145k
    return true;
71
146k
}
72
73
245
void TimezoneUtils::load_timezones_to_cache() {
74
245
    std::string base_str;
75
    // try get from system
76
245
    char* tzdir_env = std::getenv("TZDIR");
77
245
    if (tzdir_env && *tzdir_env) {
78
0
        tzdir = tzdir_env;
79
0
    }
80
81
245
    base_str = tzdir;
82
245
    base_str += '/';
83
84
245
    const auto root_path = fs::path {base_str};
85
245
    if (!exists(root_path)) {
86
0
        throw Exception(Status::FatalError("Cannot find system tzfile. Doris exiting!"));
87
0
    }
88
89
245
    std::set<std::string> ignore_paths = {"posix", "right"}; // duplications. ignore them.
90
91
152k
    for (fs::recursive_directory_iterator it {base_str}; it != end(it); it++) {
92
152k
        const auto& dir_entry = *it;
93
152k
        try {
94
152k
            if (dir_entry.is_regular_file() ||
95
152k
                (dir_entry.is_symlink() && is_regular_file(read_symlink(dir_entry)))) {
96
146k
                auto tz_name = dir_entry.path().string().substr(base_str.length());
97
146k
                if (!parse_save_name_tz(tz_name)) {
98
1.23k
                    LOG(WARNING) << "Meet illegal tzdata file: " << tz_name << ". skipped";
99
1.23k
                }
100
146k
            } else if (dir_entry.is_directory() &&
101
5.39k
                       ignore_paths.contains(dir_entry.path().filename())) {
102
490
                it.disable_recursion_pending();
103
490
            }
104
152k
        } catch (const fs::filesystem_error& e) {
105
            // maybe symlink loop or to nowhere...
106
0
            LOG(WARNING) << "filesystem error when loading timezone file from " << dir_entry.path()
107
0
                         << ": " << e.what();
108
0
        }
109
152k
    }
110
    // some special cases. Z = Zulu. CST = Asia/Shanghai
111
245
    if (auto it = lower_zone_cache_->find("zulu"); it != lower_zone_cache_->end()) {
112
245
        lower_zone_cache_->emplace("z", it->second);
113
245
    }
114
245
    if (auto it = lower_zone_cache_->find("asia/shanghai"); it != lower_zone_cache_->end()) {
115
245
        lower_zone_cache_->emplace("cst", it->second);
116
245
    }
117
118
245
    lower_zone_cache_->erase("lmt"); // local mean time for every timezone
119
120
245
    load_offsets_to_cache();
121
245
    LOG(INFO) << "Preloaded" << lower_zone_cache_->size() << " timezones.";
122
245
}
123
124
21.2k
static std::string to_hour_string(int arg) {
125
21.2k
    if (arg < 0 && arg > -10) { // -9 to -1
126
7.07k
        return std::string {"-0"} + std::to_string(std::abs(arg));
127
14.1k
    } else if (arg >= 0 && arg < 10) { //0 to 9
128
7.86k
        return std::string {"0"} + std::to_string(arg);
129
7.86k
    }
130
6.28k
    return std::to_string(arg);
131
21.2k
}
132
133
262
void TimezoneUtils::load_offsets_to_cache() {
134
262
    static constexpr int supported_minutes[] = {0, 30, 45};
135
7.33k
    for (int hour = -12; hour <= +14; hour++) {
136
21.2k
        for (int minute : supported_minutes) {
137
21.2k
            char min_str[3];
138
21.2k
            snprintf(min_str, sizeof(min_str), "%02d", minute);
139
21.2k
            std::string offset_str = (hour >= 0 ? "+" : "") + to_hour_string(hour) + ':' + min_str;
140
21.2k
            cctz::time_zone result;
141
21.2k
            parse_tz_offset_string(offset_str, result);
142
21.2k
            lower_zone_cache_->emplace(offset_str, result);
143
21.2k
        }
144
7.07k
    }
145
    // -00 for hour is also valid
146
262
    std::string offset_str = "-00:00";
147
262
    cctz::time_zone result;
148
262
    parse_tz_offset_string(offset_str, result);
149
262
    lower_zone_cache_->emplace(offset_str, result);
150
262
    offset_str = "-00:30";
151
262
    parse_tz_offset_string(offset_str, result);
152
262
    lower_zone_cache_->emplace(offset_str, result);
153
262
    offset_str = "-00:45";
154
262
    parse_tz_offset_string(offset_str, result);
155
262
    lower_zone_cache_->emplace(offset_str, result);
156
262
}
157
158
2.59M
bool TimezoneUtils::find_cctz_time_zone(const std::string& timezone, cctz::time_zone& ctz) {
159
2.59M
    if (auto it = lower_zone_cache_->find(to_lower_copy(timezone)); it != lower_zone_cache_->end())
160
2.55M
            [[likely]] {
161
2.55M
        ctz = it->second;
162
2.55M
        return true;
163
2.55M
    }
164
40.9k
    return false;
165
2.59M
}
166
167
22.0k
bool TimezoneUtils::parse_tz_offset_string(const std::string& timezone, cctz::time_zone& ctz) {
168
    // like +08:00, which not in timezone_names_map_
169
22.0k
    re2::StringPiece value;
170
22.0k
    if (time_zone_offset_format_reg.Match(timezone, 0, timezone.size(), RE2::UNANCHORED, &value, 1))
171
22.0k
            [[likely]] {
172
22.0k
        bool positive = value[0] != '-';
173
174
        //Regular expression guarantees hour and minute must be int
175
22.0k
        int hour = std::stoi(value.substr(1, 2).as_string());
176
22.0k
        int minute = std::stoi(value.substr(4, 2).as_string());
177
178
        // timezone offsets around the world extended from -12:00 to +14:00
179
22.0k
        if (!positive && hour > 12) {
180
1
            return false;
181
22.0k
        } else if (positive && hour > 14) {
182
1
            return false;
183
1
        }
184
22.0k
        int offset = hour * 60 * 60 + minute * 60;
185
22.0k
        offset *= positive ? 1 : -1;
186
22.0k
        ctz = cctz::fixed_time_zone(cctz::seconds(offset));
187
22.0k
        return true;
188
22.0k
    }
189
1
    return false;
190
22.0k
}
191
192
#include "common/compile_check_end.h"
193
} // namespace doris