blob: 77e8df922eee7c02d89e1b3122d824384f094efe [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "garnet/bin/timezone/timezone.h"
#include <zircon/syscalls.h>
#include <fstream>
#include "lib/fsl/vmo/file.h"
#include "lib/fsl/vmo/sized_vmo.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/strings/string_number_conversions.h"
#include "third_party/icu/source/common/unicode/errorcode.h"
#include "third_party/icu/source/common/unicode/udata.h"
#include "third_party/icu/source/i18n/unicode/timezone.h"
constexpr char kDefaultTimezone[] = "UTC";
constexpr int32_t kMillisecondsInMinute = 60000;
namespace time_zone {
TimezoneImpl::TimezoneImpl(std::unique_ptr<component::StartupContext> context,
const char icu_data_path[], const char tz_id_path[])
: context_(std::move(context)),
icu_data_path_(icu_data_path),
tz_id_path_(tz_id_path),
valid_(Init()) {
context_->outgoing().AddPublicService(bindings_.GetHandler(this));
}
TimezoneImpl::~TimezoneImpl() = default;
bool TimezoneImpl::Init() {
fsl::SizedVmo icu_data;
if (!fsl::VmoFromFilename(icu_data_path_, &icu_data)) {
FXL_LOG(ERROR) << "Unable to load ICU data. Timezone data unavailable.";
return false;
}
// Maps the ICU VMO into this process.
uintptr_t icu_data_ptr = 0;
if (zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ, 0, icu_data.vmo().get(),
0, icu_data.size(), &icu_data_ptr) != ZX_OK) {
FXL_LOG(ERROR) << "Unable to map ICU data into process.";
return false;
}
// ICU-related initialization.
UErrorCode icu_set_data_status = U_ZERO_ERROR;
udata_setCommonData(reinterpret_cast<void*>(icu_data_ptr),
&icu_set_data_status);
if (icu_set_data_status != U_ZERO_ERROR) {
FXL_LOG(ERROR) << "Unable to set common ICU data. "
<< "Timezone data unavailable.";
return false;
}
return true;
}
void TimezoneImpl::GetTimezoneOffsetMinutes(
int64_t milliseconds_since_epoch,
GetTimezoneOffsetMinutesCallback callback) {
if (!valid_) {
callback(0, 0);
return;
}
fidl::StringPtr timezone_id = GetTimezoneIdImpl();
std::unique_ptr<icu::TimeZone> timezone(
icu::TimeZone::createTimeZone(timezone_id.get().c_str()));
int32_t local_offset = 0, dst_offset = 0;
UErrorCode error = U_ZERO_ERROR;
// Local time is set to false, and local_offset/dst_offset/error are mutated
// via non-const references.
timezone->getOffset(static_cast<UDate>(milliseconds_since_epoch), false,
local_offset, dst_offset, error);
if (error != U_ZERO_ERROR) {
icu::ErrorCode icuError;
icuError.set(error);
FXL_LOG(ERROR) << "Unable to get correct offset: error code " << error
<< " " << icuError.errorName();
callback(0, 0);
return;
}
local_offset /= kMillisecondsInMinute;
dst_offset /= kMillisecondsInMinute;
callback(local_offset, dst_offset);
}
void TimezoneImpl::NotifyWatchers(const fidl::StringPtr& new_timezone_id) {
for (auto& watcher : watchers_) {
watcher->OnTimezoneOffsetChange(new_timezone_id);
}
}
bool TimezoneImpl::IsValidTimezoneId(const fidl::StringPtr& timezone_id) {
std::unique_ptr<icu::TimeZone> timezone(
icu::TimeZone::createTimeZone(timezone_id.get().c_str()));
if ((*timezone) == icu::TimeZone::getUnknown()) {
return false;
}
return true;
}
void TimezoneImpl::SetTimezone(std::string timezone_id,
SetTimezoneCallback callback) {
if (!valid_) {
FXL_LOG(ERROR) << "Time service is not valid.";
callback(false);
return;
}
if (!IsValidTimezoneId(timezone_id)) {
FXL_LOG(ERROR) << "Timezone '" << timezone_id << "' is not valid.";
callback(false);
return;
}
std::ofstream out_fstream(tz_id_path_, std::ofstream::trunc);
if (!out_fstream.is_open()) {
FXL_LOG(ERROR) << "Unable to open file for write '" << tz_id_path_ << "'";
callback(false);
return;
}
out_fstream << timezone_id;
out_fstream.close();
NotifyWatchers(timezone_id);
callback(true);
}
void TimezoneImpl::GetTimezoneId(GetTimezoneIdCallback callback) {
callback(GetTimezoneIdImpl());
}
fidl::StringPtr TimezoneImpl::GetTimezoneIdImpl() {
if (!valid_) {
return kDefaultTimezone;
}
std::ifstream in_fstream(tz_id_path_);
if (!in_fstream.is_open()) {
return kDefaultTimezone;
}
std::string id_str;
in_fstream >> id_str;
in_fstream.close();
if (id_str.empty()) {
FXL_LOG(ERROR) << "TZ file empty at '" << tz_id_path_ << "'";
return kDefaultTimezone;
}
if (!IsValidTimezoneId(id_str)) {
FXL_LOG(ERROR) << "Saved TZ ID invalid: '" << id_str << "'";
return kDefaultTimezone;
}
return id_str;
}
void TimezoneImpl::ReleaseWatcher(fuchsia::timezone::TimezoneWatcher* watcher) {
auto predicate = [watcher](const auto& target) {
return target.get() == watcher;
};
watchers_.erase(
std::remove_if(watchers_.begin(), watchers_.end(), predicate));
}
void TimezoneImpl::Watch(
fidl::InterfaceHandle<fuchsia::timezone::TimezoneWatcher> watcher) {
fuchsia::timezone::TimezoneWatcherPtr watcher_proxy = watcher.Bind();
fuchsia::timezone::TimezoneWatcher* proxy_raw_ptr = watcher_proxy.get();
watcher_proxy.set_error_handler([this, proxy_raw_ptr](zx_status_t status) {
ReleaseWatcher(proxy_raw_ptr);
});
watchers_.push_back(std::move(watcher_proxy));
}
} // namespace time_zone