blob: 2c8e03dc5c4f3c1278b71d05011f06384b379ba4 [file] [log] [blame]
/* Copyright (C) 2007-2008 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
*/
#include "android/utils/timezone.h"
#include "android/android.h"
#include "android/base/memory/ScopedPtr.h"
#include "android/utils/bufprint.h"
#include "android/utils/debug.h"
#include "android/utils/eintr_wrapper.h"
#include "android/base/files/PathUtils.h"
#include "android/base/system/System.h"
#include "android/base/StringFormat.h"
#include "android/base/StringView.h"
#include "android/base/memory/LazyInstance.h"
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fstream>
#include <sstream>
#include <memory>
#include <string>
#define DEBUG 1
#if 1
# define D_ACTIVE VERBOSE_CHECK(timezone)
#else
# define D_ACTIVE DEBUG
#endif
#if DEBUG
# define D(...) do { if (D_ACTIVE) fprintf(stderr, __VA_ARGS__); } while (0)
#else
# define D(...) ((void)0)
#endif
static const char* get_zoneinfo_timezone( void ); /* forward */
static char android_timezone0[256];
static const char* android_timezone;
static int android_timezone_init;
static int
check_timezone_is_zoneinfo(const char* tz)
{
const char* slash1 = NULL, *slash2 = NULL;
if (tz == NULL)
return 0;
/* the name must be of the form Area/Location or Area/Location/SubLocation */
slash1 = strchr( tz, '/' );
if (slash1 == NULL || slash1[1] == 0)
return 0;
slash2 = strchr( slash1+1, '/');
if (slash2 != NULL) {
if (slash2[1] == 0 || strchr(slash2+1, '/') != NULL)
return 0;
}
return 1;
}
char*
bufprint_zoneinfo_timezone( char* p, char* end )
{
const char* tz = get_zoneinfo_timezone();
if (tz == NULL || !check_timezone_is_zoneinfo(tz))
return bufprint(p, end, "Unknown/Unknown");
else
return bufprint(p, end, "%s", tz);
}
/* on OS X, the timezone directory is always /usr/share/zoneinfo
* this makes things easy.
*/
#if defined(__APPLE__)
#include <unistd.h>
#include <limits.h>
#define LOCALTIME_FILE "/etc/localtime"
#define ZONEINFO_DIR "/usr/share/zoneinfo/"
static const char*
get_zoneinfo_timezone( void )
{
if (!android_timezone_init) {
const char* tz = getenv("TZ");
char buff[PATH_MAX+1];
android_timezone_init = 1;
if (tz == NULL) {
int len = readlink(LOCALTIME_FILE, buff, sizeof(buff));
if (len < 0) {
dprint( "### WARNING: Could not read %s, something is very wrong on your system",
LOCALTIME_FILE);
return NULL;
}
buff[len] = 0;
D("%s: %s points to %s\n", __FUNCTION__, LOCALTIME_FILE, buff);
if ( memcmp(buff, ZONEINFO_DIR, sizeof(ZONEINFO_DIR)-1) ) {
dprint( "### WARNING: %s does not point to %s, can't determine zoneinfo timezone name",
LOCALTIME_FILE, ZONEINFO_DIR );
return NULL;
}
tz = buff + sizeof(ZONEINFO_DIR)-1;
if ( !check_timezone_is_zoneinfo(tz) ) {
dprint( "### WARNING: %s does not point to zoneinfo-compatible timezone name\n", LOCALTIME_FILE );
return NULL;
}
}
snprintf(android_timezone0, sizeof(android_timezone0), "%s", tz );
android_timezone = android_timezone0;
}
D( "found timezone %s", android_timezone );
return android_timezone;
}
#endif /* __APPLE__ */
/* on Linux, with glibc2, the zoneinfo directory can be changed with TZDIR environment variable
* but should be /usr/share/zoneinfo by default. /etc/localtime is not guaranteed to exist on
* all platforms, so if it doesn't, try $TZDIR/localtime, then /usr/share/zoneinfo/locatime
* ugly, isn't it ?
*
* besides, modern Linux distribution don't make /etc/localtime a symlink but a straight copy of
* the original timezone file. the only way to know which zoneinfo name to retrieve is to compare
* it with all files in $TZDIR (at least those that match Area/Location or Area/Location/SubLocation
*/
#if defined(__linux__) || defined (__FreeBSD__)
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define ZONEINFO_DIR "/usr/share/zoneinfo/"
#define LOCALTIME_FILE1 "/etc/localtime"
typedef struct {
const char* localtime;
struct stat localtime_st;
char* path_end;
char* path_root;
char path[ PATH_MAX ];
} ScanDataRec;
static int
compare_timezone_to_localtime( ScanDataRec* scan,
const char* path )
{
struct stat st;
int fd1, fd2, result = 0;
D( "%s: comparing %s:", __FUNCTION__, path );
if ( stat( path, &st ) < 0 ) {
D( " can't stat: %s\n", strerror(errno) );
return 0;
}
if ( st.st_size != scan->localtime_st.st_size ) {
D( " size mistmatch (%zd != %zd)\n", (size_t)st.st_size, (size_t)scan->localtime_st.st_size );
return 0;
}
fd1 = open( scan->localtime, O_RDONLY );
if (fd1 < 0) {
D(" can't open %s: %s\n", scan->localtime, strerror(errno) );
return 0;
}
fd2 = open( path, O_RDONLY );
if (fd2 < 0) {
D(" can't open %s: %s\n", path, strerror(errno) );
close(fd1);
return 0;
}
do {
off_t nn;
for (nn = 0; nn < st.st_size; nn++) {
char temp[2];
int ret;
ret = HANDLE_EINTR(read(fd1, &temp[0], 1));
if (ret < 0) break;
ret = HANDLE_EINTR(read(fd2, &temp[1], 1));
if (ret < 0) break;
if (temp[0] != temp[1])
break;
}
result = (nn == st.st_size);
} while (0);
D( result ? " MATCH\n" : "no match\n" );
close(fd2);
close(fd1);
return result;
}
static const char*
scan_timezone_dir( ScanDataRec* scan,
char* top,
int depth )
{
DIR* d = opendir( scan->path );
const char* result = NULL;
D( "%s: entering '%s\n", __FUNCTION__, scan->path );
if (d != NULL) {
struct dirent* ent;
while ((ent = readdir(d)) != NULL) {
struct stat ent_st;
char* p = top;
if (ent->d_name[0] == '.') /* avoid hidden and special files */
continue;
p = bufprint( p, scan->path_end, "/%s", ent->d_name );
if (p >= scan->path_end)
continue;
//D( "%s: scanning '%s'\n", __FUNCTION__, scan->path );
// Important: use lstat() instead of stat() because recent
// Ubuntu distributions creates directories full of links, e.g.
// /usr/share/info/posix/Australia/Sydney -> ../../Australia/Sydney
// and we want to ignore them.
if ( lstat( scan->path, &ent_st ) < 0 )
continue;
if ( S_ISDIR(ent_st.st_mode) && depth < 2 )
{
//D( "%s: directory '%s'\n", __FUNCTION__, scan->path );
result = scan_timezone_dir( scan, p, depth + 1 );
if (result != NULL)
break;
}
else if ( S_ISREG(ent_st.st_mode) && (depth >= 1 && depth <= 2) )
{
char* name = scan->path_root + 1;
if ( check_timezone_is_zoneinfo( name ) )
{
if (compare_timezone_to_localtime( scan, scan->path ))
{
result = strdup( name );
D( "%s: found '%s'\n", __FUNCTION__, result );
break;
}
}
else
{
//D( "%s: ignoring '%s'\n", __FUNCTION__, scan->path );
}
}
}
closedir(d);
}
return result;
}
static const char*
get_zoneinfo_timezone( void )
{
if (!android_timezone_init)
{
const char* tz = getenv( "TZ" );
// if we ever allocate into tz, this object will take care of it.
android::base::ScopedCPtr<const char> tzDeleter;
android_timezone_init = 1;
if ( tz != NULL && !check_timezone_is_zoneinfo(tz) ) {
D( "%s: ignoring non zoneinfo formatted TZ environment variable: '%s'\n",
__FUNCTION__, tz );
tz = NULL;
}
if (tz == NULL) {
int len;
char temp[ PATH_MAX ];
std::string tzdir;
std::string localtime;
/* determine the correct timezone directory */
{
const char* env = getenv("TZDIR");
const char* zoneinfo_dir = ZONEINFO_DIR;
if (env == NULL)
env = zoneinfo_dir;
if ( access( env, R_OK ) != 0 ) {
if ( env == zoneinfo_dir ) {
fprintf( stderr,
"### WARNING: could not find %s directory. unable to determine host timezone\n", env );
} else {
D( "%s: TZDIR does not point to valid directory, using %s instead\n",
__FUNCTION__, zoneinfo_dir );
env = zoneinfo_dir;
}
return NULL;
}
tzdir = env;
}
/* remove trailing slash, if any */
if (!tzdir.empty() && tzdir.back() == '/') {
tzdir.pop_back();
}
D( "%s: found timezone dir as %s\n", __FUNCTION__, tzdir.c_str() );
/* try to find the localtime file */
const char* localtimePtr = LOCALTIME_FILE1;
if ( access( localtimePtr, R_OK ) != 0 ) {
char *p = temp, *end = p + sizeof(temp);
p = bufprint( p, end, "%s/%s", tzdir.c_str(), "localtime" );
if (p >= end || access( temp, R_OK ) != 0 ) {
fprintf( stderr, "### WARNING: could not find %s or %s. unable to determine host timezone\n",
LOCALTIME_FILE1, temp );
goto Exit;
}
localtimePtr = temp;
}
localtime = localtimePtr;
D( "%s: found localtime file as %s\n", __FUNCTION__, localtime.c_str() );
#if 1
/* if the localtime file is a link, make a quick check */
len = readlink( localtime.c_str(), temp, sizeof(temp)-1 );
if (len >= 0 && len > static_cast<int>(tzdir.size()) + 2) {
temp[len] = 0;
/* verify that the link points to tzdir/<something> where <something> is a valid zoneinfo name */
if ( !memcmp( temp, tzdir.c_str(), tzdir.size() ) && temp[tzdir.size()] == '/' ) {
if ( check_timezone_is_zoneinfo( temp + tzdir.size() + 1 ) ) {
/* we have it ! */
tz = temp + tzdir.size() + 1;
D( "%s: found zoneinfo timezone %s from %s symlink\n", __FUNCTION__, tz, localtime.c_str() );
goto Exit;
}
D( "%s: %s link points to non-zoneinfo filename %s, comparing contents\n",
__FUNCTION__, localtime.c_str(), temp );
}
}
#endif
/* otherwise, parse all files under tzdir and see if we have something that looks like it */
{
ScanDataRec scan[1];
if ( stat( localtime.c_str(), &scan->localtime_st ) < 0 ) {
fprintf( stderr, "### WARNING: can't access '%s', unable to determine host timezone\n",
localtime.c_str() );
goto Exit;
}
scan->localtime = localtime.c_str();
scan->path_end = scan->path + sizeof(scan->path);
scan->path_root = bufprint( scan->path, scan->path_end, "%s", tzdir.c_str() );
tz = scan_timezone_dir( scan, scan->path_root, 0 );
tzDeleter.reset(tz);
}
Exit:
if (tz == NULL)
return NULL;
}
snprintf(android_timezone0, sizeof(android_timezone0), "%s", tz);
android_timezone = android_timezone0;
D( "found timezone %s\n", android_timezone );
}
return android_timezone;
}
#endif /* __linux__ */
/* on Windows, we need to translate the Windows timezone into a ZoneInfo one */
#ifdef _WIN32
#include "android/base/files/ScopedRegKey.h"
#include "android/base/system/Win32Utils.h"
#include <windows.h>
typedef struct {
const char* win_name;
const char* zoneinfo_name;
} Win32Timezone;
/* table generated from http://www.unicode.org/cldr/charts/latest/supplemental/zone_tzid.html */
static const Win32Timezone _win32_timezones[] = {
{ "AUS Central Standard Time", "Australia/Darwin" },
{ "AUS Eastern Standard Time", "Australia/Sydney" },
{ "AUS Eastern Standard Time", "Australia/Melbourne" },
{ "Afghanistan Standard Time", "Asia/Kabul" },
{ "Alaskan Standard Time", "America/Anchorage" },
{ "Alaskan Standard Time", "America/Juneau" },
{ "Alaskan Standard Time", "America/Metlakatla" },
{ "Alaskan Standard Time", "America/Nome" },
{ "Alaskan Standard Time", "America/Sitka" },
{ "Alaskan Standard Time", "America/Yakutat" },
{ "Aleutian Standard Time", "America/Adak" },
{ "Altai Standard Time", "Asia/Barnaul" },
{ "Arab Standard Time", "Asia/Riyadh" },
{ "Arab Standard Time", "Asia/Bahrain" },
{ "Arab Standard Time", "Asia/Kuwait" },
{ "Arab Standard Time", "Asia/Qatar" },
{ "Arab Standard Time", "Asia/Aden" },
{ "Arabian Standard Time", "Asia/Dubai" },
{ "Arabian Standard Time", "Asia/Muscat" },
{ "Arabian Standard Time", "Etc/GMT-4" },
{ "Arabic Standard Time", "Asia/Baghdad" },
{ "Argentina Standard Time", "America/Buenos_Aires" },
{ "Argentina Standard Time", "America/Argentina/La_Rioja" },
{ "Argentina Standard Time", "America/Argentina/Rio_Gallegos" },
{ "Argentina Standard Time", "America/Argentina/Salta" },
{ "Argentina Standard Time", "America/Argentina/San_Juan" },
{ "Argentina Standard Time", "America/Argentina/San_Luis" },
{ "Argentina Standard Time", "America/Argentina/Tucuman" },
{ "Argentina Standard Time", "America/Argentina/Ushuaia" },
{ "Argentina Standard Time", "America/Catamarca" },
{ "Argentina Standard Time", "America/Cordoba" },
{ "Argentina Standard Time", "America/Jujuy" },
{ "Argentina Standard Time", "America/Mendoza" },
{ "Astrakhan Standard Time", "Europe/Astrakhan" },
{ "Astrakhan Standard Time", "Europe/Ulyanovsk" },
{ "Atlantic Standard Time", "America/Halifax" },
{ "Atlantic Standard Time", "Atlantic/Bermuda" },
{ "Atlantic Standard Time", "America/Glace_Bay" },
{ "Atlantic Standard Time", "America/Goose_Bay" },
{ "Atlantic Standard Time", "America/Moncton" },
{ "Atlantic Standard Time", "America/Thule" },
{ "Aus Central W. Standard Time", "Australia/Eucla" },
{ "Azerbaijan Standard Time", "Asia/Baku" },
{ "Azores Standard Time", "Atlantic/Azores" },
{ "Azores Standard Time", "America/Scoresbysund" },
{ "Bahia Standard Time", "America/Bahia" },
{ "Bangladesh Standard Time", "Asia/Dhaka" },
{ "Bangladesh Standard Time", "Asia/Thimphu" },
{ "Belarus Standard Time", "Europe/Minsk" },
{ "Bougainville Standard Time", "Pacific/Bougainville" },
{ "Canada Central Standard Time", "America/Regina" },
{ "Canada Central Standard Time", "America/Swift_Current" },
{ "Cape Verde Standard Time", "Atlantic/Cape_Verde" },
{ "Cape Verde Standard Time", "Etc/GMT+1" },
{ "Caucasus Standard Time", "Asia/Yerevan" },
{ "Cen. Australia Standard Time", "Australia/Adelaide" },
{ "Cen. Australia Standard Time", "Australia/Broken_Hill" },
{ "Central America Standard Time", "America/Guatemala" },
{ "Central America Standard Time", "America/Belize" },
{ "Central America Standard Time", "America/Costa_Rica" },
{ "Central America Standard Time", "Pacific/Galapagos" },
{ "Central America Standard Time", "America/Tegucigalpa" },
{ "Central America Standard Time", "America/Managua" },
{ "Central America Standard Time", "America/El_Salvador" },
{ "Central America Standard Time", "Etc/GMT+6" },
{ "Central Asia Standard Time", "Asia/Almaty" },
{ "Central Asia Standard Time", "Antarctica/Vostok" },
{ "Central Asia Standard Time", "Asia/Urumqi" },
{ "Central Asia Standard Time", "Indian/Chagos" },
{ "Central Asia Standard Time", "Asia/Bishkek" },
{ "Central Asia Standard Time", "Asia/Qyzylorda" },
{ "Central Asia Standard Time", "Etc/GMT-6" },
{ "Central Brazilian Standard Time", "America/Cuiaba" },
{ "Central Brazilian Standard Time", "America/Campo_Grande" },
{ "Central Europe Standard Time", "Europe/Budapest" },
{ "Central Europe Standard Time", "Europe/Tirane" },
{ "Central Europe Standard Time", "Europe/Prague" },
{ "Central Europe Standard Time", "Europe/Podgorica" },
{ "Central Europe Standard Time", "Europe/Belgrade" },
{ "Central Europe Standard Time", "Europe/Ljubljana" },
{ "Central Europe Standard Time", "Europe/Bratislava" },
{ "Central European Standard Time", "Europe/Warsaw" },
{ "Central European Standard Time", "Europe/Sarajevo" },
{ "Central European Standard Time", "Europe/Zagreb" },
{ "Central European Standard Time", "Europe/Skopje" },
{ "Central Pacific Standard Time", "Pacific/Guadalcanal" },
{ "Central Pacific Standard Time", "Antarctica/Macquarie" },
{ "Central Pacific Standard Time", "Pacific/Ponape" },
{ "Central Pacific Standard Time", "Pacific/Kosrae" },
{ "Central Pacific Standard Time", "Pacific/Noumea" },
{ "Central Pacific Standard Time", "Pacific/Efate" },
{ "Central Pacific Standard Time", "Etc/GMT-11" },
{ "Central Standard Time", "America/Chicago" },
{ "Central Standard Time", "America/Winnipeg" },
{ "Central Standard Time", "America/Rainy_River" },
{ "Central Standard Time", "America/Rankin_Inlet" },
{ "Central Standard Time", "America/Resolute" },
{ "Central Standard Time", "America/Matamoros" },
{ "Central Standard Time", "America/Indiana/Knox" },
{ "Central Standard Time", "America/Indiana/Tell_City" },
{ "Central Standard Time", "America/Menominee" },
{ "Central Standard Time", "America/North_Dakota/Beulah" },
{ "Central Standard Time", "America/North_Dakota/Center" },
{ "Central Standard Time", "America/North_Dakota/New_Salem" },
{ "Central Standard Time", "CST6CDT" },
{ "Central Standard Time (Mexico)", "America/Mexico_City" },
{ "Central Standard Time (Mexico)", "America/Bahia_Banderas" },
{ "Central Standard Time (Mexico)", "America/Merida" },
{ "Central Standard Time (Mexico)", "America/Monterrey" },
{ "Chatham Islands Standard Time", "Pacific/Chatham" },
{ "China Standard Time", "Asia/Shanghai" },
{ "China Standard Time", "Asia/Hong_Kong" },
{ "China Standard Time", "Asia/Macau" },
{ "Cuba Standard Time", "America/Havana" },
{ "Dateline Standard Time", "Etc/GMT+12" },
{ "E. Africa Standard Time", "Africa/Nairobi" },
{ "E. Africa Standard Time", "Antarctica/Syowa" },
{ "E. Africa Standard Time", "Africa/Djibouti" },
{ "E. Africa Standard Time", "Africa/Asmera" },
{ "E. Africa Standard Time", "Africa/Addis_Ababa" },
{ "E. Africa Standard Time", "Indian/Comoro" },
{ "E. Africa Standard Time", "Indian/Antananarivo" },
{ "E. Africa Standard Time", "Africa/Khartoum" },
{ "E. Africa Standard Time", "Africa/Mogadishu" },
{ "E. Africa Standard Time", "Africa/Juba" },
{ "E. Africa Standard Time", "Africa/Dar_es_Salaam" },
{ "E. Africa Standard Time", "Africa/Kampala" },
{ "E. Africa Standard Time", "Indian/Mayotte" },
{ "E. Africa Standard Time", "Etc/GMT-3" },
{ "E. Australia Standard Time", "Australia/Brisbane" },
{ "E. Australia Standard Time", "Australia/Lindeman" },
{ "E. Europe Standard Time", "Europe/Chisinau" },
{ "E. South America Standard Time", "America/Sao_Paulo" },
{ "Easter Island Standard Time", "Pacific/Easter" },
{ "Eastern Standard Time", "America/New_York" },
{ "Eastern Standard Time", "America/Nassau" },
{ "Eastern Standard Time", "America/Toronto" },
{ "Eastern Standard Time", "America/Iqaluit" },
{ "Eastern Standard Time", "America/Montreal" },
{ "Eastern Standard Time", "America/Nipigon" },
{ "Eastern Standard Time", "America/Pangnirtung" },
{ "Eastern Standard Time", "America/Thunder_Bay" },
{ "Eastern Standard Time", "America/Detroit" },
{ "Eastern Standard Time", "America/Indiana/Petersburg" },
{ "Eastern Standard Time", "America/Indiana/Vincennes" },
{ "Eastern Standard Time", "America/Indiana/Winamac" },
{ "Eastern Standard Time", "America/Kentucky/Monticello" },
{ "Eastern Standard Time", "America/Louisville" },
{ "Eastern Standard Time", "EST5EDT" },
{ "Eastern Standard Time (Mexico)", "America/Cancun" },
{ "Egypt Standard Time", "Africa/Cairo" },
{ "Ekaterinburg Standard Time", "Asia/Yekaterinburg" },
{ "FLE Standard Time", "Europe/Kiev" },
{ "FLE Standard Time", "Europe/Mariehamn" },
{ "FLE Standard Time", "Europe/Sofia" },
{ "FLE Standard Time", "Europe/Tallinn" },
{ "FLE Standard Time", "Europe/Helsinki" },
{ "FLE Standard Time", "Europe/Vilnius" },
{ "FLE Standard Time", "Europe/Riga" },
{ "FLE Standard Time", "Europe/Uzhgorod" },
{ "FLE Standard Time", "Europe/Zaporozhye" },
{ "Fiji Standard Time", "Pacific/Fiji" },
{ "GMT Standard Time", "Europe/London" },
{ "GMT Standard Time", "Atlantic/Canary" },
{ "GMT Standard Time", "Atlantic/Faeroe" },
{ "GMT Standard Time", "Europe/Guernsey" },
{ "GMT Standard Time", "Europe/Dublin" },
{ "GMT Standard Time", "Europe/Isle_of_Man" },
{ "GMT Standard Time", "Europe/Jersey" },
{ "GMT Standard Time", "Europe/Lisbon" },
{ "GMT Standard Time", "Atlantic/Madeira" },
{ "GTB Standard Time", "Europe/Bucharest" },
{ "GTB Standard Time", "Asia/Nicosia" },
{ "GTB Standard Time", "Europe/Athens" },
{ "Georgian Standard Time", "Asia/Tbilisi" },
{ "Greenland Standard Time", "America/Godthab" },
{ "Greenwich Standard Time", "Atlantic/Reykjavik" },
{ "Greenwich Standard Time", "Africa/Ouagadougou" },
{ "Greenwich Standard Time", "Africa/Abidjan" },
{ "Greenwich Standard Time", "Africa/Accra" },
{ "Greenwich Standard Time", "Africa/Banjul" },
{ "Greenwich Standard Time", "Africa/Conakry" },
{ "Greenwich Standard Time", "Africa/Bissau" },
{ "Greenwich Standard Time", "Africa/Monrovia" },
{ "Greenwich Standard Time", "Africa/Bamako" },
{ "Greenwich Standard Time", "Africa/Nouakchott" },
{ "Greenwich Standard Time", "Atlantic/St_Helena" },
{ "Greenwich Standard Time", "Africa/Freetown" },
{ "Greenwich Standard Time", "Africa/Dakar" },
{ "Greenwich Standard Time", "Africa/Sao_Tome" },
{ "Greenwich Standard Time", "Africa/Lome" },
{ "Haiti Standard Time", "America/Port-au-Prince" },
{ "Hawaiian Standard Time", "Pacific/Honolulu" },
{ "Hawaiian Standard Time", "Pacific/Rarotonga" },
{ "Hawaiian Standard Time", "Pacific/Tahiti" },
{ "Hawaiian Standard Time", "Pacific/Johnston" },
{ "Hawaiian Standard Time", "Etc/GMT+10" },
{ "India Standard Time", "Asia/Calcutta" },
{ "Iran Standard Time", "Asia/Tehran" },
{ "Israel Standard Time", "Asia/Jerusalem" },
{ "Jordan Standard Time", "Asia/Amman" },
{ "Kaliningrad Standard Time", "Europe/Kaliningrad" },
{ "Korea Standard Time", "Asia/Seoul" },
{ "Libya Standard Time", "Africa/Tripoli" },
{ "Line Islands Standard Time", "Pacific/Kiritimati" },
{ "Line Islands Standard Time", "Etc/GMT-14" },
{ "Lord Howe Standard Time", "Australia/Lord_Howe" },
{ "Magadan Standard Time", "Asia/Magadan" },
{ "Marquesas Standard Time", "Pacific/Marquesas" },
{ "Mauritius Standard Time", "Indian/Mauritius" },
{ "Mauritius Standard Time", "Indian/Reunion" },
{ "Mauritius Standard Time", "Indian/Mahe" },
{ "Middle East Standard Time", "Asia/Beirut" },
{ "Montevideo Standard Time", "America/Montevideo" },
{ "Morocco Standard Time", "Africa/Casablanca" },
{ "Morocco Standard Time", "Africa/El_Aaiun" },
{ "Mountain Standard Time", "America/Denver" },
{ "Mountain Standard Time", "America/Edmonton" },
{ "Mountain Standard Time", "America/Cambridge_Bay" },
{ "Mountain Standard Time", "America/Inuvik" },
{ "Mountain Standard Time", "America/Yellowknife" },
{ "Mountain Standard Time", "America/Ojinaga" },
{ "Mountain Standard Time", "America/Boise" },
{ "Mountain Standard Time", "MST7MDT" },
{ "Mountain Standard Time (Mexico)", "America/Chihuahua" },
{ "Mountain Standard Time (Mexico)", "America/Mazatlan" },
{ "Myanmar Standard Time", "Asia/Rangoon" },
{ "Myanmar Standard Time", "Indian/Cocos" },
{ "N. Central Asia Standard Time", "Asia/Novosibirsk" },
{ "N. Central Asia Standard Time", "Asia/Omsk" },
{ "Namibia Standard Time", "Africa/Windhoek" },
{ "Nepal Standard Time", "Asia/Katmandu" },
{ "New Zealand Standard Time", "Pacific/Auckland" },
{ "New Zealand Standard Time", "Antarctica/McMurdo" },
{ "Newfoundland Standard Time", "America/St_Johns" },
{ "Norfolk Standard Time", "Pacific/Norfolk" },
{ "North Asia East Standard Time", "Asia/Irkutsk" },
{ "North Asia Standard Time", "Asia/Krasnoyarsk" },
{ "North Asia Standard Time", "Asia/Novokuznetsk" },
{ "North Korea Standard Time", "Asia/Pyongyang" },
{ "Pacific SA Standard Time", "America/Santiago" },
{ "Pacific SA Standard Time", "Antarctica/Palmer" },
{ "Pacific Standard Time", "America/Los_Angeles" },
{ "Pacific Standard Time", "America/Vancouver" },
{ "Pacific Standard Time", "America/Dawson" },
{ "Pacific Standard Time", "America/Whitehorse" },
{ "Pacific Standard Time", "PST8PDT" },
{ "Pacific Standard Time (Mexico)", "America/Tijuana" },
{ "Pacific Standard Time (Mexico)", "America/Santa_Isabel" },
{ "Pakistan Standard Time", "Asia/Karachi" },
{ "Paraguay Standard Time", "America/Asuncion" },
{ "Romance Standard Time", "Europe/Paris" },
{ "Romance Standard Time", "Europe/Brussels" },
{ "Romance Standard Time", "Europe/Copenhagen" },
{ "Romance Standard Time", "Europe/Madrid" },
{ "Romance Standard Time", "Africa/Ceuta" },
{ "Russia Time Zone 10", "Asia/Srednekolymsk" },
{ "Russia Time Zone 11", "Asia/Kamchatka" },
{ "Russia Time Zone 11", "Asia/Anadyr" },
{ "Russia Time Zone 3", "Europe/Samara" },
{ "Russian Standard Time", "Europe/Moscow" },
{ "Russian Standard Time", "Europe/Kirov" },
{ "Russian Standard Time", "Europe/Simferopol" },
{ "Russian Standard Time", "Europe/Volgograd" },
{ "SA Eastern Standard Time", "America/Cayenne" },
{ "SA Eastern Standard Time", "Antarctica/Rothera" },
{ "SA Eastern Standard Time", "America/Fortaleza" },
{ "SA Eastern Standard Time", "America/Belem" },
{ "SA Eastern Standard Time", "America/Maceio" },
{ "SA Eastern Standard Time", "America/Recife" },
{ "SA Eastern Standard Time", "America/Santarem" },
{ "SA Eastern Standard Time", "Atlantic/Stanley" },
{ "SA Eastern Standard Time", "America/Paramaribo" },
{ "SA Eastern Standard Time", "Etc/GMT+3" },
{ "SA Pacific Standard Time", "America/Bogota" },
{ "SA Pacific Standard Time", "America/Rio_Branco" },
{ "SA Pacific Standard Time", "America/Eirunepe" },
{ "SA Pacific Standard Time", "America/Coral_Harbour" },
{ "SA Pacific Standard Time", "America/Guayaquil" },
{ "SA Pacific Standard Time", "America/Jamaica" },
{ "SA Pacific Standard Time", "America/Cayman" },
{ "SA Pacific Standard Time", "America/Panama" },
{ "SA Pacific Standard Time", "America/Lima" },
{ "SA Pacific Standard Time", "Etc/GMT+5" },
{ "SA Western Standard Time", "America/La_Paz" },
{ "SA Western Standard Time", "America/Antigua" },
{ "SA Western Standard Time", "America/Anguilla" },
{ "SA Western Standard Time", "America/Aruba" },
{ "SA Western Standard Time", "America/Barbados" },
{ "SA Western Standard Time", "America/St_Barthelemy" },
{ "SA Western Standard Time", "America/Kralendijk" },
{ "SA Western Standard Time", "America/Manaus" },
{ "SA Western Standard Time", "America/Boa_Vista" },
{ "SA Western Standard Time", "America/Porto_Velho" },
{ "SA Western Standard Time", "America/Blanc-Sablon" },
{ "SA Western Standard Time", "America/Curacao" },
{ "SA Western Standard Time", "America/Dominica" },
{ "SA Western Standard Time", "America/Santo_Domingo" },
{ "SA Western Standard Time", "America/Grenada" },
{ "SA Western Standard Time", "America/Guadeloupe" },
{ "SA Western Standard Time", "America/Guyana" },
{ "SA Western Standard Time", "America/St_Kitts" },
{ "SA Western Standard Time", "America/St_Lucia" },
{ "SA Western Standard Time", "America/Marigot" },
{ "SA Western Standard Time", "America/Martinique" },
{ "SA Western Standard Time", "America/Montserrat" },
{ "SA Western Standard Time", "America/Puerto_Rico" },
{ "SA Western Standard Time", "America/Lower_Princes" },
{ "SA Western Standard Time", "America/Port_of_Spain" },
{ "SA Western Standard Time", "America/St_Vincent" },
{ "SA Western Standard Time", "America/Tortola" },
{ "SA Western Standard Time", "America/St_Thomas" },
{ "SA Western Standard Time", "Etc/GMT+4" },
{ "SE Asia Standard Time", "Asia/Bangkok" },
{ "SE Asia Standard Time", "Antarctica/Davis" },
{ "SE Asia Standard Time", "Indian/Christmas" },
{ "SE Asia Standard Time", "Asia/Jakarta" },
{ "SE Asia Standard Time", "Asia/Pontianak" },
{ "SE Asia Standard Time", "Asia/Phnom_Penh" },
{ "SE Asia Standard Time", "Asia/Vientiane" },
{ "SE Asia Standard Time", "Asia/Saigon" },
{ "SE Asia Standard Time", "Etc/GMT-7" },
{ "Saint Pierre Standard Time", "America/Miquelon" },
{ "Sakhalin Standard Time", "Asia/Sakhalin" },
{ "Samoa Standard Time", "Pacific/Apia" },
{ "Singapore Standard Time", "Asia/Singapore" },
{ "Singapore Standard Time", "Asia/Brunei" },
{ "Singapore Standard Time", "Asia/Makassar" },
{ "Singapore Standard Time", "Asia/Kuala_Lumpur" },
{ "Singapore Standard Time", "Asia/Kuching" },
{ "Singapore Standard Time", "Asia/Manila" },
{ "Singapore Standard Time", "Etc/GMT-8" },
{ "South Africa Standard Time", "Africa/Johannesburg" },
{ "South Africa Standard Time", "Africa/Bujumbura" },
{ "South Africa Standard Time", "Africa/Gaborone" },
{ "South Africa Standard Time", "Africa/Lubumbashi" },
{ "South Africa Standard Time", "Africa/Maseru" },
{ "South Africa Standard Time", "Africa/Blantyre" },
{ "South Africa Standard Time", "Africa/Maputo" },
{ "South Africa Standard Time", "Africa/Kigali" },
{ "South Africa Standard Time", "Africa/Mbabane" },
{ "South Africa Standard Time", "Africa/Lusaka" },
{ "South Africa Standard Time", "Africa/Harare" },
{ "South Africa Standard Time", "Etc/GMT-2" },
{ "Sri Lanka Standard Time", "Asia/Colombo" },
{ "Syria Standard Time", "Asia/Damascus" },
{ "Taipei Standard Time", "Asia/Taipei" },
{ "Tasmania Standard Time", "Australia/Hobart" },
{ "Tasmania Standard Time", "Australia/Currie" },
{ "Tocantins Standard Time", "America/Araguaina" },
{ "Tokyo Standard Time", "Asia/Tokyo" },
{ "Tokyo Standard Time", "Asia/Jayapura" },
{ "Tokyo Standard Time", "Pacific/Palau" },
{ "Tokyo Standard Time", "Asia/Dili" },
{ "Tokyo Standard Time", "Etc/GMT-9" },
{ "Tomsk Standard Time", "Asia/Tomsk" },
{ "Tonga Standard Time", "Pacific/Tongatapu" },
{ "Tonga Standard Time", "Pacific/Enderbury" },
{ "Tonga Standard Time", "Pacific/Fakaofo" },
{ "Tonga Standard Time", "Etc/GMT-13" },
{ "Transbaikal Standard Time", "Asia/Chita" },
{ "Turkey Standard Time", "Europe/Istanbul" },
{ "Turks And Caicos Standard Time", "America/Grand_Turk" },
{ "US Eastern Standard Time", "America/Indianapolis" },
{ "US Eastern Standard Time", "America/Indiana/Marengo" },
{ "US Eastern Standard Time", "America/Indiana/Vevay" },
{ "US Mountain Standard Time", "America/Phoenix" },
{ "US Mountain Standard Time", "America/Dawson_Creek" },
{ "US Mountain Standard Time", "America/Creston" },
{ "US Mountain Standard Time", "America/Fort_Nelson" },
{ "US Mountain Standard Time", "America/Hermosillo" },
{ "US Mountain Standard Time", "Etc/GMT+7" },
{ "UTC", "Etc/GMT" },
{ "UTC", "America/Danmarkshavn" },
{ "UTC+12", "Etc/GMT-12" },
{ "UTC+12", "Pacific/Tarawa" },
{ "UTC+12", "Pacific/Majuro" },
{ "UTC+12", "Pacific/Kwajalein" },
{ "UTC+12", "Pacific/Nauru" },
{ "UTC+12", "Pacific/Funafuti" },
{ "UTC+12", "Pacific/Wake" },
{ "UTC+12", "Pacific/Wallis" },
{ "UTC-02", "Etc/GMT+2" },
{ "UTC-02", "America/Noronha" },
{ "UTC-02", "Atlantic/South_Georgia" },
{ "UTC-08", "Etc/GMT+8" },
{ "UTC-08", "Pacific/Pitcairn" },
{ "UTC-09", "Etc/GMT+9" },
{ "UTC-09", "Pacific/Gambier" },
{ "UTC-11", "Etc/GMT+11" },
{ "UTC-11", "Pacific/Pago_Pago" },
{ "UTC-11", "Pacific/Niue" },
{ "UTC-11", "Pacific/Midway" },
{ "Ulaanbaatar Standard Time", "Asia/Ulaanbaatar" },
{ "Ulaanbaatar Standard Time", "Asia/Choibalsan" },
{ "Venezuela Standard Time", "America/Caracas" },
{ "Vladivostok Standard Time", "Asia/Vladivostok" },
{ "Vladivostok Standard Time", "Asia/Ust-Nera" },
{ "W. Australia Standard Time", "Australia/Perth" },
{ "W. Australia Standard Time", "Antarctica/Casey" },
{ "W. Central Africa Standard Time", "Africa/Lagos" },
{ "W. Central Africa Standard Time", "Africa/Luanda" },
{ "W. Central Africa Standard Time", "Africa/Porto-Novo" },
{ "W. Central Africa Standard Time", "Africa/Kinshasa" },
{ "W. Central Africa Standard Time", "Africa/Bangui" },
{ "W. Central Africa Standard Time", "Africa/Brazzaville" },
{ "W. Central Africa Standard Time", "Africa/Douala" },
{ "W. Central Africa Standard Time", "Africa/Algiers" },
{ "W. Central Africa Standard Time", "Africa/Libreville" },
{ "W. Central Africa Standard Time", "Africa/Malabo" },
{ "W. Central Africa Standard Time", "Africa/Niamey" },
{ "W. Central Africa Standard Time", "Africa/Ndjamena" },
{ "W. Central Africa Standard Time", "Africa/Tunis" },
{ "W. Central Africa Standard Time", "Etc/GMT-1" },
{ "W. Europe Standard Time", "Europe/Berlin" },
{ "W. Europe Standard Time", "Europe/Andorra" },
{ "W. Europe Standard Time", "Europe/Vienna" },
{ "W. Europe Standard Time", "Europe/Zurich" },
{ "W. Europe Standard Time", "Europe/Busingen" },
{ "W. Europe Standard Time", "Europe/Gibraltar" },
{ "W. Europe Standard Time", "Europe/Rome" },
{ "W. Europe Standard Time", "Europe/Vaduz" },
{ "W. Europe Standard Time", "Europe/Luxembourg" },
{ "W. Europe Standard Time", "Europe/Monaco" },
{ "W. Europe Standard Time", "Europe/Malta" },
{ "W. Europe Standard Time", "Europe/Amsterdam" },
{ "W. Europe Standard Time", "Europe/Oslo" },
{ "W. Europe Standard Time", "Europe/Stockholm" },
{ "W. Europe Standard Time", "Arctic/Longyearbyen" },
{ "W. Europe Standard Time", "Europe/San_Marino" },
{ "W. Europe Standard Time", "Europe/Vatican" },
{ "W. Mongolia Standard Time", "Asia/Hovd" },
{ "West Asia Standard Time", "Asia/Tashkent" },
{ "West Asia Standard Time", "Antarctica/Mawson" },
{ "West Asia Standard Time", "Asia/Oral" },
{ "West Asia Standard Time", "Asia/Aqtau" },
{ "West Asia Standard Time", "Asia/Aqtobe" },
{ "West Asia Standard Time", "Indian/Maldives" },
{ "West Asia Standard Time", "Indian/Kerguelen" },
{ "West Asia Standard Time", "Asia/Dushanbe" },
{ "West Asia Standard Time", "Asia/Ashgabat" },
{ "West Asia Standard Time", "Asia/Samarkand" },
{ "West Asia Standard Time", "Etc/GMT-5" },
{ "West Bank Standard Time", "Asia/Hebron" },
{ "West Bank Standard Time", "Asia/Gaza" },
{ "West Pacific Standard Time", "Pacific/Port_Moresby" },
{ "West Pacific Standard Time", "Antarctica/DumontDUrville" },
{ "West Pacific Standard Time", "Pacific/Truk" },
{ "West Pacific Standard Time", "Pacific/Guam" },
{ "West Pacific Standard Time", "Pacific/Saipan" },
{ "West Pacific Standard Time", "Etc/GMT-10" },
{ "Yakutsk Standard Time", "Asia/Yakutsk" },
{ "Yakutsk Standard Time", "Asia/Khandyga" },
{ NULL, NULL }
};
static const char*
get_zoneinfo_timezone( void )
{
if (!android_timezone_init)
{
char tzname[128];
time_t t = time(NULL);
struct tm* tm = localtime(&t);
const Win32Timezone* win32tz = _win32_timezones;
android_timezone_init = 1;
if (!tm) {
D("%s: could not determine current date/time\n", __FUNCTION__);
return NULL;
}
memset(tzname, 0, sizeof(tzname));
strftime(tzname, sizeof(tzname) - 1, "%Z", tm);
for (win32tz = _win32_timezones; win32tz->win_name != NULL; win32tz++)
if ( !strcmp(win32tz->win_name, tzname) ) {
android_timezone = win32tz->zoneinfo_name;
goto Exit;
}
#if 0 /* TODO */
/* we didn't find it, this may come from localized versions of Windows. we're going to explore the registry,
* as the code in Postgresql does...
*/
#endif
D( "%s: could not determine current timezone\n", __FUNCTION__ );
return NULL;
}
Exit:
D( "emulator: found timezone %s\n", android_timezone );
return android_timezone;
}
#endif /* _WIN32 */
namespace {
static constexpr android::base::StringView kMonthName[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
class TimeZone {
public:
TimeZone();
/* Return local time based on android timezone set by function androidTimeZoneSet().
* Fall back to host OS localtime() if android timezone is not set.
* */
struct tm* androidLocaltime(const time_t& timeNow);
/* Return the timezone offset including day light saving in seconds with respect to
* UTC in guest android OS. Fall back to use host OS timezone offset if android time
* zone is not set or invalid.
* */
long androidTimeZoneOffset(const time_t& timeNow);
/* Try to set the default android OS timezone. This operation will affect the
* emulated networked time in virtual modem.
* When |tzname| is not found or invalid, fall back to use host OS local timezone.
* */
int androidTimeZoneSet(const char* tzname);
private:
struct tm mStandardDateUtc;
struct tm mDaylightDateUtc;
long mStandardOffset;
long mDaylightOffset;
int mCurrentYear;
int mAndroidTimeZoneInit;
std::string mTimeZoneName;
static const int kSecondsPerDay = 60 * 60 * 24;
long getTimeZoneDiff(const time_t& timeNow);
/*
* Return 1 if current timezone is in daylight saving,
* 0 if current timezone is NOT in daylight saving.
* -1 if current timezone does NOT have daylight saving
* */
int getIsDaylightSavingTime(const time_t& timeNow);
/*
* Return 1 if struct tm a is bigger than struct tm b
* 0 if equal
* -1 if struct tm a is less than struct tm b
* */
static int utcCompare(const struct tm& tm1, const struct tm& tm2);
/*
* Return the struct tm difference in seconds assuming the diff is within 24 hours and
* |end| and |beginning| represent utc time.
* */
static long diffTime(const struct tm& end,const struct tm& beginning);
#ifdef _WIN32
static void parseSystemTime(const SYSTEMTIME& winTime, struct tm* androidTime);
/*
* Translate tzid to windows timezone name. Then retrieve time zone
* information from registry. Does NOT work on WINE.
* */
static int parseTimeZoneInformationFromRegistry(const char* winName, TIME_ZONE_INFORMATION* tzi);
#else // !_WIN32
/*
* Sample format: Mar 13 07:00:00 2016 and the input needs to be tokenized beforehand
* */
static int parseZdumpDate(const std::vector<std::string>& tokens, struct tm* date);
/*
* Run sample shell cmd: zdump -v America/New_York | grep 2016 to retrive timezone information
* Sample Output:
* America/New_York Sun Mar 13 06:59:59 2016 UT = Sun Mar 13 01:59:59 2016 EST isdst=0 [gmtoff=-18000]
* America/New_York Sun Mar 13 07:00:00 2016 UT = Sun Mar 13 03:00:00 2016 EDT isdst=1 [gmtoff=-14400]
* America/New_York Sun Nov 6 05:59:59 2016 UT = Sun Nov 6 01:59:59 2016 EDT isdst=1 [gmtoff=-14400]
* America/New_York Sun Nov 6 06:00:00 2016 UT = Sun Nov 6 01:00:00 2016 EST isdst=0 [gmtoff=-18000]
*
* Caution: gmtoff is not shown on darwin.
* Return 0 if the timezone is found and has daylight saving given the current
* year. Otherwise return -1
* */
int setAndroidTimeZoneUsingZdump();
/*
* Run sample shell cmd: TZ=America/New_York date +%:z to retrive timezone information
* Sample output: -0400
*
* Assuming that the timezone doesn't have daylight saving,
* Return 0 if the timezone is found otherwise return -1.
* */
int setAndroidTimeZoneUsingDate();
#endif
};
TimeZone::TimeZone(){
time_t timeNow = time(NULL);
//initialize the current
mCurrentYear = gmtime(&timeNow)->tm_year + 1900;
mAndroidTimeZoneInit = 0;
}
long TimeZone::androidTimeZoneOffset(const time_t& timeNow)
{
if (this->mAndroidTimeZoneInit) {
//reset android timezone if year has changed
int year = gmtime(&timeNow)->tm_year + 1900;
if (year != mCurrentYear){
mCurrentYear = year;
androidTimeZoneSet(mTimeZoneName.c_str());
}
return getTimeZoneDiff(timeNow);
} else {
struct tm local = *localtime(&timeNow);
struct tm utc = *gmtime(&timeNow);
return this->diffTime(local, utc);
}
}
struct tm* TimeZone::androidLocaltime(const time_t& timeNow)
{
if (this->mAndroidTimeZoneInit) {
//reset android timezone if year has changed
int year = gmtime(&timeNow)->tm_year + 1900;
if (year != mCurrentYear) {
mCurrentYear = year;
androidTimeZoneSet(mTimeZoneName.c_str());
}
int isdst = getIsDaylightSavingTime(timeNow);
long tzdiff = getTimeZoneDiff(timeNow);
time_t localTime = timeNow + tzdiff;
struct tm* local = NULL;
local = gmtime(&localTime);
local->tm_isdst = isdst;
return local;
} else {
return localtime(&timeNow);
}
}
int TimeZone::utcCompare(const struct tm& utc1, const struct tm& utc2)
{
if (utc1.tm_year > utc2.tm_year) {
return 1;
} else if (utc1.tm_year < utc2.tm_year) {
return -1;
} else {
if (utc1.tm_mon > utc2.tm_mon) {
return 1;
} else if (utc1.tm_mon < utc2.tm_mon) {
return -1;
} else {
if (utc1.tm_mday > utc2.tm_mday) {
return 1;
} else if (utc1.tm_mday < utc2.tm_mday) {
return -1;
} else {
if (utc1.tm_hour > utc2.tm_hour) {
return 1;
} else if (utc1.tm_hour < utc2.tm_hour) {
return -1;
} else {
if (utc1.tm_min > utc2.tm_min) {
return 1;
} else if (utc1.tm_min < utc2.tm_min) {
return -1;
} else {
if (utc1.tm_sec > utc2.tm_sec) {
return 1;
} else if (utc1.tm_sec < utc2.tm_sec) {
return -1;
} else {
return 0;
}
}
}
}
}
}
}
int TimeZone::getIsDaylightSavingTime(const time_t& timeNow)
{
if (mDaylightOffset == 0) {
return -1;
} else {
struct tm utc = *gmtime(&timeNow);
if (utcCompare(utc, mStandardDateUtc) < 0 || utcCompare(utc, mDaylightDateUtc) >= 0) {
return 0;
} else {
return 1;
}
}
}
long TimeZone::getTimeZoneDiff(const time_t& timeNow)
{
int isdst = getIsDaylightSavingTime(timeNow);
return (isdst <= 0) ? mStandardOffset : mStandardOffset + mDaylightOffset;
}
long TimeZone::diffTime(const struct tm& end, const struct tm& beginning)
{
long endInSecs = end.tm_sec + 60 * (end.tm_min + 60 * end.tm_hour);
long beginningInSecs = beginning.tm_sec + 60 * (beginning.tm_min + 60 * beginning.tm_hour);
if (end.tm_year > beginning.tm_year) {
endInSecs += kSecondsPerDay;
} else if (end.tm_year < beginning.tm_year) {
beginningInSecs += kSecondsPerDay;
} else {
if (end.tm_mon > beginning.tm_mon) {
endInSecs += kSecondsPerDay;
} else if (end.tm_mon < beginning.tm_mon) {
beginningInSecs += kSecondsPerDay;
} else {
endInSecs += kSecondsPerDay * end.tm_mday;
beginningInSecs += kSecondsPerDay * beginning.tm_mday;
}
}
return endInSecs - beginningInSecs;
}
#ifdef _WIN32
/* Struct referenced from https://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx */
typedef struct _REG_TZI_FORMAT
{
LONG Bias;
LONG StandardBias;
LONG DaylightBias;
SYSTEMTIME StandardDate;
SYSTEMTIME DaylightDate;
}REG_TZI_FORMAT;
int TimeZone::parseTimeZoneInformationFromRegistry(const char* winName, TIME_ZONE_INFORMATION* tzi)
{
using namespace android::base;
using android::base::ScopedRegKey;
using android::base::Win32Utils;
HKEY hkey = 0;
std::string registryPath = StringFormat("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\%s", winName);
LONG result = RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
registryPath.c_str(),
0,
KEY_READ,
&hkey);
if (result != ERROR_SUCCESS) {
std::string errorString = Win32Utils::getErrorString(result);
D( "RegOpenKeyEx failed %li %s\n", result, errorString.c_str());
return -1;
}
REG_TZI_FORMAT binaryTzi;
DWORD dataType;
DWORD len = sizeof(binaryTzi);
ScopedRegKey timezoneKey(hkey);
result = RegQueryValueEx(
timezoneKey.get(),
"TZI",
NULL,
&dataType,
(LPBYTE) &binaryTzi,
&len);
if (result != ERROR_SUCCESS || dataType != REG_BINARY) {
std::string errorString = Win32Utils::getErrorString(result);
D( "RegQueryValueEx failed %li %s\n", result, errorString.c_str());
return -1;
}
tzi->Bias = binaryTzi.Bias;
tzi->DaylightBias = binaryTzi.DaylightBias;
tzi->DaylightDate = binaryTzi.DaylightDate;
tzi->StandardBias = binaryTzi.StandardBias;
tzi->StandardDate = binaryTzi.StandardDate;
return 0;
}
void TimeZone::parseSystemTime(const SYSTEMTIME& winTime, struct tm* androidTime)
{
androidTime->tm_year = (int)winTime.wYear - 1900;
androidTime->tm_mon = (int)winTime.wMonth - 1;
androidTime->tm_mday = (int)winTime.wDay;
androidTime->tm_hour = (int)winTime.wHour;
androidTime->tm_min = (int)winTime.wMinute;
androidTime->tm_sec = (int)winTime.wSecond;
}
#else
int TimeZone::parseZdumpDate(const std::vector<std::string>& tokens, struct tm* date)
{
if (tokens.size() >= 4) {
int monIdx = -1;
for(int i = 0; i < 12; i++) {
if (!tokens[0].compare(kMonthName[i])) {
monIdx = i;
break;
}
}
if (monIdx == -1) { return -1; }
date->tm_mon = monIdx;
date->tm_mday = std::stoi(tokens[1]);
date->tm_hour = std::stoi(tokens[2].substr(0,2));
date->tm_min = std::stoi(tokens[2].substr(3, 2));
date->tm_sec = std::stoi(tokens[2].substr(6,2));
date->tm_year = std::stoi(tokens[3]) - 1900;
return 0;
}
else {
return -1;
}
}
int TimeZone::setAndroidTimeZoneUsingZdump()
{
using namespace android::base;
std::string zdumpCmd = StringFormat(
"zdump -v %s | grep %d", mTimeZoneName, mCurrentYear);
std::vector<std::string> shellCmd = {"/bin/bash", "-c", std::move(zdumpCmd)};
RunOptions runFlags = System::RunOptions::WaitForCompletion |
System::RunOptions::TerminateOnTimeout |
System::RunOptions::DumpOutputToFile;
System::Duration timeout = 1000;
System::ProcessExitCode exitCode;
const std::string outputFile = PathUtils::join(
System::get()->getTempDir(),
StringFormat("android_tzoffset%d",
(int)System::get()->getCurrentProcessId()));
bool commandRan = System::get()->runCommand(
shellCmd, runFlags, timeout, &exitCode, nullptr, outputFile);
if (commandRan && !exitCode) {
std::ifstream file(outputFile.c_str());
std::string line;
std::vector<std::string> rules;
while (std::getline(file, line)) {
rules.push_back(std::move(line));
}
long utcOffsetDST = 0;
long utcOffsetStandard = 0;
if (rules.size() == 4) {
//tokenize only the 2nd and 4th rule
for(int i = 1; i < 4; i += 2) {
std::stringstream ss(rules[i]);
std::vector<std::string> tokens;
std::string token;
while (ss >> token) {
tokens.push_back(std::move(token));
}
std::vector<std::string> utcTokens(tokens.begin() + 2, tokens.begin() + 6);
if (parseZdumpDate(utcTokens, i == 1 ? &mStandardDateUtc : &mDaylightDateUtc)) {
return -1;
}
struct tm localDate;
std::vector<std::string> localTokens(tokens.begin() + 9, tokens.begin() + 13);
if (parseZdumpDate(localTokens, &localDate)) {
return -1;
}
if (i == 1) {
utcOffsetDST = this->diffTime(localDate, mStandardDateUtc);
} else {
utcOffsetStandard = this->diffTime(localDate, mDaylightDateUtc);
}
}
mStandardOffset = utcOffsetStandard;
mDaylightOffset = utcOffsetDST - utcOffsetStandard;
return 0;
}
}
return -1;
}
int TimeZone::setAndroidTimeZoneUsingDate()
{
using namespace android::base;
std::string dateCmd = StringFormat("TZ=%s date +%%z", mTimeZoneName.c_str());
std::vector<std::string> shellCmd = {"/bin/bash", "-c", std::move(dateCmd)};
RunOptions runFlags = System::RunOptions::WaitForCompletion |
System::RunOptions::TerminateOnTimeout |
System::RunOptions::DumpOutputToFile;
System::Duration timeout = 1000;
System::ProcessExitCode exitCode;
const std::string outputFile = PathUtils::join(
System::get()->getTempDir(),
StringFormat("android_tzoffset%d",
(int)System::get()->getCurrentProcessId()));
bool commandRan = System::get()->runCommand(
shellCmd, runFlags, timeout, &exitCode, nullptr, outputFile);
if (commandRan && !exitCode) {
std::ifstream file(outputFile.c_str());
std::string tzdiff;
std::getline(file, tzdiff);
file.close();
int sign = (tzdiff[0] == '+') ? 1 : -1;
mStandardOffset = sign * (60 * (std::stoi(tzdiff.substr(1, 2)) * 60 + std::stoi(tzdiff.substr(3,2))));
mDaylightOffset = 0;
return 0;
} else {
return -1;
}
}
#endif
int TimeZone::androidTimeZoneSet(const char* tzname)
{
mTimeZoneName = std::string(tzname);
this->mAndroidTimeZoneInit = 0;
#ifdef _WIN32
const char* winName = NULL;
for (const Win32Timezone* win32tz = _win32_timezones; win32tz->win_name != NULL; win32tz++) {
if (!strcmp(win32tz->zoneinfo_name, tzname)) {
winName = win32tz->win_name;
break;
}
}
if (winName) {
TIME_ZONE_INFORMATION win_tzi;
if (parseTimeZoneInformationFromRegistry(winName, &win_tzi)) {
D( "%s: could not retrieve time zone information from registry on Windows, use host localtime by default.\n", __FUNCTION__ );
} else {
//UTC = localtime + Bias
mStandardOffset = -win_tzi.StandardBias * 60;
mDaylightOffset = -win_tzi.DaylightBias * 60;
if (mDaylightOffset != 0) {
parseSystemTime(win_tzi.StandardDate, &mStandardDateUtc);
parseSystemTime(win_tzi.DaylightDate, &mStandardDateUtc);
}
this->mAndroidTimeZoneInit = 1;
}
} else {
D( "%s: could not determine current timezone\n", __FUNCTION__ );
}
#else
if (setAndroidTimeZoneUsingZdump() && setAndroidTimeZoneUsingDate()) {
D( "%s: could not retrieve time zone information from zdump or date "
"command, use host localtime by default.\n", __func__);
} else {
this->mAndroidTimeZoneInit = 1;
}
#endif
return this->mAndroidTimeZoneInit ? 0 : -1;
}
android::base::LazyInstance<TimeZone> sAndroidTimeZone = LAZY_INSTANCE_INIT;
} //namespace
int
timezone_set(const char *tzname)
{
int len;
android_timezone_init = 0;
if (!check_timezone_is_zoneinfo(tzname)){ return -1; }
len = strlen(tzname);
if (len > (int)sizeof(android_timezone0)-1) { return -1; }
strcpy(android_timezone0, tzname);
android_timezone = android_timezone0;
if (sAndroidTimeZone->androidTimeZoneSet(tzname)) {
return -1;
} else {
android_timezone_init = 1;
return 0;
}
}
long
android_tzoffset_in_seconds(time_t* time_p)
{
time_t time = *time_p;
return sAndroidTimeZone->androidTimeZoneOffset(time);
}
struct tm* android_localtime(time_t* time_p)
{
time_t time = *time_p;
return sAndroidTimeZone->androidLocaltime(time);
}