Serenity Operating System
at master 240 lines 7.1 kB view raw
1/* 2 * Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/Debug.h> 8#include <AK/DeprecatedString.h> 9#include <LibTimeZone/TimeZone.h> 10#include <limits.h> 11#include <stdio.h> 12#include <stdlib.h> 13#include <string.h> 14#include <time.h> 15#include <unistd.h> 16 17namespace TimeZone { 18 19// NOTE: Without ENABLE_TIME_ZONE_DATA LibTimeZone operates in a UTC-only mode and only recognizes 20// the 'UTC' time zone, which is slightly more useful than a bunch of dummy functions that 21// can't do anything. When we build with time zone data, these weakly linked functions are 22// replaced with their proper counterparts. 23 24#if !ENABLE_TIME_ZONE_DATA 25enum class TimeZone : u16 { 26 UTC, 27}; 28#endif 29 30class TimeZoneFile { 31public: 32 TimeZoneFile(char const* mode) 33 : m_file(fopen("/etc/timezone", mode)) 34 { 35 if (m_file) 36 flockfile(m_file); 37 } 38 39 ~TimeZoneFile() 40 { 41 if (m_file) { 42 funlockfile(m_file); 43 fclose(m_file); 44 } 45 } 46 47 ErrorOr<DeprecatedString> read_time_zone() 48 { 49 if (!m_file) 50 return Error::from_string_literal("Could not open /etc/timezone"); 51 52 Array<u8, 128> buffer; 53 size_t bytes = fread(buffer.data(), 1, buffer.size(), m_file); 54 55 if (bytes == 0) 56 return Error::from_string_literal("Could not read time zone from /etc/timezone"); 57 58 return DeprecatedString(buffer.span().slice(0, bytes)).trim_whitespace(); 59 } 60 61 ErrorOr<void> write_time_zone(StringView time_zone) 62 { 63 if (!m_file) 64 return Error::from_string_literal("Could not open /etc/timezone"); 65 66 auto bytes = fwrite(time_zone.characters_without_null_termination(), 1, time_zone.length(), m_file); 67 if (bytes != time_zone.length()) 68 return Error::from_string_literal("Could not write new time zone to /etc/timezone"); 69 70 return {}; 71 } 72 73private: 74 FILE* m_file { nullptr }; 75}; 76 77StringView system_time_zone() 78{ 79 TimeZoneFile time_zone_file("r"); 80 auto time_zone = time_zone_file.read_time_zone(); 81 82 // FIXME: Propagate the error to existing callers. 83 if (time_zone.is_error()) { 84 dbgln_if(TIME_ZONE_DEBUG, "{}", time_zone.error()); 85 return "UTC"sv; 86 } 87 88 return canonicalize_time_zone(time_zone.value()).value_or("UTC"sv); 89} 90 91StringView current_time_zone() 92{ 93 if (char* tz = getenv("TZ"); tz != nullptr) { 94 // FIXME: Actually parse the TZ environment variable, described here: 95 // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08 96 StringView time_zone { tz, strlen(tz) }; 97 98 if (auto maybe_time_zone = canonicalize_time_zone(time_zone); maybe_time_zone.has_value()) 99 return *maybe_time_zone; 100 101 dbgln_if(TIME_ZONE_DEBUG, "Could not determine time zone from TZ environment: {}", time_zone); 102 return "UTC"sv; 103 } 104 105#ifdef AK_OS_SERENITY 106 return system_time_zone(); 107#else 108 static constexpr auto zoneinfo = "/zoneinfo/"sv; 109 char buffer[PATH_MAX]; 110 111 if (realpath("/etc/localtime", buffer)) { 112 auto time_zone = StringView { buffer, strlen(buffer) }; 113 114 if (auto index = time_zone.find(zoneinfo); index.has_value()) 115 time_zone = time_zone.substring_view(*index + zoneinfo.length()); 116 117 if (auto maybe_time_zone = canonicalize_time_zone(time_zone); maybe_time_zone.has_value()) 118 return *maybe_time_zone; 119 120 dbgln_if(TIME_ZONE_DEBUG, "Could not determine time zone from /etc/localtime: {}", time_zone); 121 } else { 122 dbgln_if(TIME_ZONE_DEBUG, "Could not read the /etc/localtime link: {}", strerror(errno)); 123 } 124 125 return "UTC"sv; 126#endif 127} 128 129ErrorOr<void> change_time_zone([[maybe_unused]] StringView time_zone) 130{ 131#ifdef AK_OS_SERENITY 132 TimeZoneFile time_zone_file("w"); 133 134 if (auto new_time_zone = canonicalize_time_zone(time_zone); new_time_zone.has_value()) 135 return time_zone_file.write_time_zone(*new_time_zone); 136 137 return Error::from_string_literal("Provided time zone is not supported"); 138#else 139 // Do not even attempt to change the time zone of someone's host machine. 140 return {}; 141#endif 142} 143 144ReadonlySpan<StringView> __attribute__((weak)) all_time_zones() 145{ 146#if !ENABLE_TIME_ZONE_DATA 147 static constexpr auto utc = Array { "UTC"sv }; 148 return utc; 149#else 150 return {}; 151#endif 152} 153 154Optional<TimeZone> __attribute__((weak)) time_zone_from_string([[maybe_unused]] StringView time_zone) 155{ 156#if !ENABLE_TIME_ZONE_DATA 157 if (time_zone.equals_ignoring_ascii_case("UTC"sv)) 158 return TimeZone::UTC; 159#endif 160 return {}; 161} 162 163StringView __attribute__((weak)) time_zone_to_string([[maybe_unused]] TimeZone time_zone) 164{ 165#if !ENABLE_TIME_ZONE_DATA 166 VERIFY(time_zone == TimeZone::UTC); 167 return "UTC"sv; 168#else 169 return {}; 170#endif 171} 172 173Optional<StringView> canonicalize_time_zone(StringView time_zone) 174{ 175 auto maybe_time_zone = time_zone_from_string(time_zone); 176 if (!maybe_time_zone.has_value()) 177 return {}; 178 179 auto canonical_time_zone = time_zone_to_string(*maybe_time_zone); 180 if (canonical_time_zone.is_one_of("Etc/UTC"sv, "Etc/GMT"sv)) 181 return "UTC"sv; 182 183 return canonical_time_zone; 184} 185 186Optional<DaylightSavingsRule> __attribute__((weak)) daylight_savings_rule_from_string(StringView) { return {}; } 187StringView __attribute__((weak)) daylight_savings_rule_to_string(DaylightSavingsRule) { return {}; } 188 189Optional<Offset> __attribute__((weak)) get_time_zone_offset([[maybe_unused]] TimeZone time_zone, AK::Time) 190{ 191#if !ENABLE_TIME_ZONE_DATA 192 VERIFY(time_zone == TimeZone::UTC); 193 return Offset {}; 194#else 195 return {}; 196#endif 197} 198 199Optional<Offset> get_time_zone_offset(StringView time_zone, AK::Time time) 200{ 201 if (auto maybe_time_zone = time_zone_from_string(time_zone); maybe_time_zone.has_value()) 202 return get_time_zone_offset(*maybe_time_zone, time); 203 return {}; 204} 205 206Optional<Array<NamedOffset, 2>> __attribute__((weak)) get_named_time_zone_offsets([[maybe_unused]] TimeZone time_zone, AK::Time) 207{ 208#if !ENABLE_TIME_ZONE_DATA 209 VERIFY(time_zone == TimeZone::UTC); 210 211 NamedOffset utc_offset {}; 212 utc_offset.name = "UTC"sv; 213 214 return Array { utc_offset, utc_offset }; 215#else 216 return {}; 217#endif 218} 219 220Optional<Array<NamedOffset, 2>> get_named_time_zone_offsets(StringView time_zone, AK::Time time) 221{ 222 if (auto maybe_time_zone = time_zone_from_string(time_zone); maybe_time_zone.has_value()) 223 return get_named_time_zone_offsets(*maybe_time_zone, time); 224 return {}; 225} 226 227Optional<Location> __attribute__((weak)) get_time_zone_location(TimeZone) { return {}; } 228 229Optional<Location> get_time_zone_location(StringView time_zone) 230{ 231 if (auto maybe_time_zone = time_zone_from_string(time_zone); maybe_time_zone.has_value()) 232 return get_time_zone_location(*maybe_time_zone); 233 return {}; 234} 235 236Optional<Region> __attribute__((weak)) region_from_string(StringView) { return {}; } 237StringView __attribute__((weak)) region_to_string(Region) { return {}; } 238Vector<StringView> __attribute__((weak)) time_zones_in_region(StringView) { return {}; } 239 240}