239 lines
6.8 KiB
C
239 lines
6.8 KiB
C
/* tzset.c: Convert current Windows timezone to POSIX timezone information.
|
|
|
|
This file is part of Cygwin.
|
|
|
|
This software is a copyrighted work licensed under the terms of the
|
|
Cygwin license. Please consult the file "CYGWIN_LICENSE" for
|
|
details. */
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <inttypes.h>
|
|
#include <wchar.h>
|
|
#include <locale.h>
|
|
#include <getopt.h>
|
|
#include <cygwin/version.h>
|
|
#include <windows.h>
|
|
|
|
#ifndef GEOID_NOT_AVAILABLE
|
|
#define GEOID_NOT_AVAILABLE -1
|
|
#endif
|
|
|
|
/* The auto-generated tzmap.h contains the mapping table from Windows timezone
|
|
and country per ISO 3166-1 to POSIX timezone. For more info, see the file
|
|
itself. */
|
|
#include "tzmap.h"
|
|
|
|
#define TZMAP_SIZE (sizeof tzmap / sizeof tzmap[0])
|
|
|
|
static struct option longopts[] =
|
|
{
|
|
{"help", no_argument, NULL, 'h' },
|
|
{"version", no_argument, NULL, 'V'},
|
|
{NULL, 0, NULL, 0}
|
|
};
|
|
|
|
static char opts[] = "hV";
|
|
|
|
#define REG_TZINFO L"SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation"
|
|
#define REG_TZDB L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones"
|
|
|
|
static inline HKEY
|
|
reg_open (HKEY pkey, PCWSTR path, const char *msg)
|
|
{
|
|
LONG ret;
|
|
HKEY hkey;
|
|
|
|
ret = RegOpenKeyExW (pkey, path, 0, KEY_READ, &hkey);
|
|
if (ret == ERROR_SUCCESS)
|
|
return hkey;
|
|
if (msg)
|
|
fprintf (stderr, "%s: cannot open registry %s, error code %" PRIu32 "\n",
|
|
program_invocation_short_name, msg, (unsigned int) ret);
|
|
return NULL;
|
|
}
|
|
|
|
/* For symmetry */
|
|
#define reg_close(hkey) RegCloseKey(hkey)
|
|
|
|
static inline BOOL
|
|
reg_query (HKEY hkey, PCWSTR value, PWCHAR buf, DWORD size, const char *msg)
|
|
{
|
|
LONG ret;
|
|
DWORD type;
|
|
|
|
ret = RegQueryValueExW (hkey, value, 0, &type, (LPBYTE) buf, &size);
|
|
if (ret == ERROR_SUCCESS)
|
|
return TRUE;
|
|
if (msg)
|
|
fprintf (stderr, "%s: cannot query registry %s, error code %" PRIu32 "\n",
|
|
program_invocation_short_name, msg, (unsigned int) ret);
|
|
return FALSE;
|
|
}
|
|
|
|
static inline BOOL
|
|
reg_enum (HKEY hkey, int idx, PWCHAR name, DWORD size)
|
|
{
|
|
return RegEnumKeyExW (hkey, idx, name, &size, NULL, NULL, NULL, NULL)
|
|
== ERROR_SUCCESS;
|
|
}
|
|
|
|
static void __attribute__ ((__noreturn__))
|
|
usage (FILE *stream)
|
|
{
|
|
fprintf (stream, ""
|
|
"Usage: %1$s [OPTION]\n"
|
|
"\n"
|
|
"Print POSIX-compatible timezone ID from current Windows timezone setting\n"
|
|
"\n"
|
|
"Options:\n"
|
|
" -h, --help output usage information and exit.\n"
|
|
" -V, --version output version information and exit.\n"
|
|
"\n"
|
|
"Use %1$s to set your TZ variable. In POSIX-compatible shells like bash,\n"
|
|
"dash, mksh, or zsh:\n"
|
|
"\n"
|
|
" export TZ=$(%1$s)\n"
|
|
"\n"
|
|
"In csh-compatible shells like tcsh:\n"
|
|
"\n"
|
|
" setenv TZ `%1$s`\n"
|
|
"\n", program_invocation_short_name);
|
|
exit (stream == stdout ? 0 : 1);
|
|
};
|
|
|
|
static void
|
|
print_version ()
|
|
{
|
|
printf ("tzset (cygwin) %d.%d.%d\n"
|
|
"POSIX-timezone generator\n"
|
|
"Copyright (C) 2012 - %s Cygwin Authors\n"
|
|
"This is free software; see the source for copying conditions. There is NO\n"
|
|
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
|
|
CYGWIN_VERSION_DLL_MAJOR / 1000,
|
|
CYGWIN_VERSION_DLL_MAJOR % 1000,
|
|
CYGWIN_VERSION_DLL_MINOR,
|
|
strrchr (__DATE__, ' ') + 1);
|
|
}
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
BOOL ret;
|
|
HKEY hkey, skey;
|
|
WCHAR keyname[256], stdname[256], std2name[256], country[10], *spc;
|
|
GEOID geo;
|
|
int opt, idx, gotit = -1;
|
|
|
|
setlocale (LC_ALL, "");
|
|
while ((opt = getopt_long (argc, argv, opts, longopts, NULL)) != EOF)
|
|
switch (opt)
|
|
{
|
|
case 'h':
|
|
usage (stdout);
|
|
case 'V':
|
|
print_version ();
|
|
return 0;
|
|
default:
|
|
fprintf (stderr, "Try `%s --help' for more information.\n",
|
|
program_invocation_short_name);
|
|
return 1;
|
|
}
|
|
if (optind < argc)
|
|
usage (stderr);
|
|
|
|
/* First fetch current timezone information from registry. */
|
|
hkey = reg_open (HKEY_LOCAL_MACHINE, REG_TZINFO, "timezone information");
|
|
if (!hkey)
|
|
return 1;
|
|
/* Vista introduced the TimeZoneKeyName value, which simplifies the
|
|
job a lot. */
|
|
if (!reg_query (hkey, L"TimeZoneKeyName", keyname, sizeof keyname, NULL))
|
|
{
|
|
/* Pre-Vista we have a lot more to do. First fetch the name of the
|
|
Standard (non-DST) timezone. If we can't get that, give up. */
|
|
if (!reg_query (hkey, L"StandardName", stdname, sizeof stdname,
|
|
"timezone information"))
|
|
{
|
|
reg_close (hkey);
|
|
return 1;
|
|
}
|
|
reg_close (hkey);
|
|
/* Now open the timezone database registry key. Every subkey is a
|
|
timezone. The key name is what we're after, but to find the right
|
|
one, we have to compare the name of the previously fetched
|
|
"StandardName" with the "Std" value in the timezone info... */
|
|
hkey = reg_open (HKEY_LOCAL_MACHINE, REG_TZDB, "timezone database");
|
|
if (!hkey)
|
|
return 1;
|
|
for (idx = 0; reg_enum (hkey, idx, keyname, sizeof keyname); ++idx)
|
|
{
|
|
skey = reg_open (hkey, keyname, NULL);
|
|
if (skey)
|
|
{
|
|
/* ...however, on MUI-enabled machines, the names are not stored
|
|
directly in the above StandardName, rather it is a resource
|
|
pointer into tzres.dll. This is stored in MUI_Std.
|
|
Fortunately it's easy to recognize this situation: If
|
|
StandardName starts with @, it's a resource pointer, otherwise
|
|
it's the cleartext value. */
|
|
ret = reg_query (skey, stdname[0] == L'@' ? L"MUI_Std" : L"Std",
|
|
std2name, sizeof std2name, NULL);
|
|
reg_close (skey);
|
|
if (ret && !wcscmp (stdname, std2name))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
reg_close (hkey);
|
|
|
|
/* We fetch the current Geo-location of the user and convert it to an
|
|
ISO 3166-1 compatible nation code. */
|
|
*country = L'\0';
|
|
geo = GetUserGeoID (GEOCLASS_NATION);
|
|
if (geo != GEOID_NOT_AVAILABLE)
|
|
GetGeoInfoW (geo, GEO_ISO2, country, sizeof country / sizeof (*country), 0);
|
|
/* If, for some reason, the Geo-location isn't available, we use the locale
|
|
setting instead. */
|
|
if (!*country)
|
|
GetLocaleInfoW (LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME,
|
|
country, sizeof country);
|
|
|
|
/* Now iterate over the mapping table and find the right entry. */
|
|
for (idx = 0; idx < TZMAP_SIZE; ++idx)
|
|
{
|
|
if (!wcscasecmp (keyname, tzmap[idx].win_tzkey))
|
|
{
|
|
if (gotit < 0)
|
|
gotit = idx;
|
|
if (!wcscasecmp (country, tzmap[idx].country))
|
|
break;
|
|
}
|
|
else if (gotit >= 0)
|
|
{
|
|
idx = gotit;
|
|
break;
|
|
}
|
|
}
|
|
if (idx >= TZMAP_SIZE)
|
|
{
|
|
if (gotit < 0)
|
|
{
|
|
fprintf (stderr,
|
|
"%s: can't find matching POSIX timezone for "
|
|
"Windows timezone \"%ls\"\n",
|
|
program_invocation_short_name, keyname);
|
|
return 1;
|
|
}
|
|
idx = gotit;
|
|
}
|
|
/* Got one. Print it.
|
|
Note: The tzmap array is in the R/O data section on x86_64. Don't
|
|
try to overwrite the space, as the code did originally. */
|
|
spc = wcschr (tzmap[idx].posix_tzid, L' ');
|
|
if (!spc)
|
|
spc = wcschr (tzmap[idx].posix_tzid, L'\0');
|
|
printf ("%.*ls\n", (int) (spc - tzmap[idx].posix_tzid), tzmap[idx].posix_tzid);
|
|
return 0;
|
|
}
|