// 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
//
// 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 "time_zone_fixed.h"
#include <algorithm>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <string>
namespace absl {
namespace time_internal {
namespace cctz {
namespace {
// The prefix used for the internal names of fixed-offset zones.
const char kFixedOffsetPrefix[] = "Fixed/";
int Parse02d(const char* p) {
static const char kDigits[] = "0123456789";
if (const char* ap = std::strchr(kDigits, *p)) {
int v = static_cast<int>(ap - kDigits);
if (const char* bp = std::strchr(kDigits, *++p)) {
return (v * 10) + static_cast<int>(bp - kDigits);
}
}
return -1;
}
} // namespace
bool FixedOffsetFromName(const std::string& name, sys_seconds* offset) {
if (name.compare(0, std::string::npos, "UTC", 3) == 0) {
*offset = sys_seconds::zero();
return true;
}
const std::size_t prefix_len = sizeof(kFixedOffsetPrefix) - 1;
const char* const ep = kFixedOffsetPrefix + prefix_len;
if (name.size() != prefix_len + 12) // "<prefix>UTC+99:99:99"
return false;
if (!std::equal(kFixedOffsetPrefix, ep, name.begin()))
return false;
const char* np = name.data() + prefix_len;
if (*np++ != 'U' || *np++ != 'T' || *np++ != 'C')
return false;
if (np[0] != '+' && np[0] != '-')
return false;
if (np[3] != ':' || np[6] != ':') // see note below about large offsets
return false;
int hours = Parse02d(np + 1);
if (hours == -1) return false;
int mins = Parse02d(np + 4);
if (mins == -1) return false;
int secs = Parse02d(np + 7);
if (secs == -1) return false;
secs += ((hours * 60) + mins) * 60;
if (secs > 24 * 60 * 60) return false; // outside supported offset range
*offset = sys_seconds(secs * (np[0] == '-' ? -1 : 1)); // "-" means west
return true;
}
std::string FixedOffsetToName(const sys_seconds& offset) {
if (offset == sys_seconds::zero()) return "UTC";
if (offset < std::chrono::hours(-24) || offset > std::chrono::hours(24)) {
// We don't support fixed-offset zones more than 24 hours
// away from UTC to avoid complications in rendering such
// offsets and to (somewhat) limit the total number of zones.
return "UTC";
}
int seconds = static_cast<int>(offset.count());
const char sign = (seconds < 0 ? '-' : '+');
int minutes = seconds / 60;
seconds %= 60;
if (sign == '-') {
if (seconds > 0) {
seconds -= 60;
minutes += 1;
}
seconds = -seconds;
minutes = -minutes;
}
int hours = minutes / 60;
minutes %= 60;
char buf[sizeof(kFixedOffsetPrefix) + sizeof("UTC-24:00:00")];
snprintf(buf, sizeof(buf), "%sUTC%c%02d:%02d:%02d",
kFixedOffsetPrefix, sign, hours, minutes, seconds);
return buf;
}
std::string FixedOffsetToAbbr(const sys_seconds& offset) {
std::string abbr = FixedOffsetToName(offset);
const std::size_t prefix_len = sizeof(kFixedOffsetPrefix) - 1;
const char* const ep = kFixedOffsetPrefix + prefix_len;
if (abbr.size() >= prefix_len) {
if (std::equal(kFixedOffsetPrefix, ep, abbr.begin())) {
abbr.erase(0, prefix_len);
if (abbr.size() == 12) { // UTC+99:99:99
abbr.erase(9, 1); // UTC+99:9999
abbr.erase(6, 1); // UTC+999999
if (abbr[8] == '0' && abbr[9] == '0') { // UTC+999900
abbr.erase(8, 2); // UTC+9999
if (abbr[6] == '0' && abbr[7] == '0') { // UTC+9900
abbr.erase(6, 2); // UTC+99
if (abbr[4] == '0') { // UTC+09
abbr.erase(4, 1); // UTC+9
}
}
}
}
}
}
return abbr;
}
} // namespace cctz
} // namespace time_internal
} // namespace absl