1 /* tzset.c: Convert current Windows timezone to POSIX timezone information.
3 This file is part of Cygwin.
5 This software is a copyrighted work licensed under the terms of the
6 Cygwin license. Please consult the file "CYGWIN_LICENSE" for
15 #include <cygwin/version.h>
18 #ifndef GEOID_NOT_AVAILABLE
19 #define GEOID_NOT_AVAILABLE -1
22 /* The auto-generated tzmap.h contains the mapping table from Windows timezone
23 and country per ISO 3166-1 to POSIX timezone. For more info, see the file
27 #define TZMAP_SIZE (sizeof tzmap / sizeof tzmap[0])
29 static struct option longopts
[] =
31 {"help", no_argument
, NULL
, 'h' },
32 {"version", no_argument
, NULL
, 'V'},
36 static char opts
[] = "hV";
38 #define REG_TZINFO L"SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation"
39 #define REG_TZDB L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones"
42 reg_open (HKEY pkey
, PCWSTR path
, const char *msg
)
47 ret
= RegOpenKeyExW (pkey
, path
, 0, KEY_READ
, &hkey
);
48 if (ret
== ERROR_SUCCESS
)
51 fprintf (stderr
, "%s: cannot open registry %s, error code %" PRIu32
"\n",
52 program_invocation_short_name
, msg
, (unsigned int) ret
);
57 #define reg_close(hkey) RegCloseKey(hkey)
60 reg_query (HKEY hkey
, PCWSTR value
, PWCHAR buf
, DWORD size
, const char *msg
)
65 ret
= RegQueryValueExW (hkey
, value
, 0, &type
, (LPBYTE
) buf
, &size
);
66 if (ret
== ERROR_SUCCESS
)
69 fprintf (stderr
, "%s: cannot query registry %s, error code %" PRIu32
"\n",
70 program_invocation_short_name
, msg
, (unsigned int) ret
);
75 reg_enum (HKEY hkey
, int idx
, PWCHAR name
, DWORD size
)
77 return RegEnumKeyExW (hkey
, idx
, name
, &size
, NULL
, NULL
, NULL
, NULL
)
81 static void __attribute__ ((__noreturn__
))
85 "Usage: %1$s [OPTION]\n"
87 "Print POSIX-compatible timezone ID from current Windows timezone setting\n"
90 " -h, --help output usage information and exit.\n"
91 " -V, --version output version information and exit.\n"
93 "Use %1$s to set your TZ variable. In POSIX-compatible shells like bash,\n"
94 "dash, mksh, or zsh:\n"
96 " export TZ=$(%1$s)\n"
98 "In csh-compatible shells like tcsh:\n"
100 " setenv TZ `%1$s`\n"
101 "\n", program_invocation_short_name
);
102 exit (stream
== stdout
? 0 : 1);
108 printf ("tzset (cygwin) %d.%d.%d\n"
109 "POSIX-timezone generator\n"
110 "Copyright (C) 2012 - %s Cygwin Authors\n"
111 "This is free software; see the source for copying conditions. There is NO\n"
112 "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
113 CYGWIN_VERSION_DLL_MAJOR
/ 1000,
114 CYGWIN_VERSION_DLL_MAJOR
% 1000,
115 CYGWIN_VERSION_DLL_MINOR
,
116 strrchr (__DATE__
, ' ') + 1);
120 main (int argc
, char **argv
)
124 WCHAR keyname
[256], stdname
[256], std2name
[256], country
[10], *spc
;
126 int opt
, idx
, gotit
= -1;
128 setlocale (LC_ALL
, "");
129 while ((opt
= getopt_long (argc
, argv
, opts
, longopts
, NULL
)) != EOF
)
138 fprintf (stderr
, "Try `%s --help' for more information.\n",
139 program_invocation_short_name
);
145 /* First fetch current timezone information from registry. */
146 hkey
= reg_open (HKEY_LOCAL_MACHINE
, REG_TZINFO
, "timezone information");
149 /* Vista introduced the TimeZoneKeyName value, which simplifies the
151 if (!reg_query (hkey
, L
"TimeZoneKeyName", keyname
, sizeof keyname
, NULL
))
153 /* Pre-Vista we have a lot more to do. First fetch the name of the
154 Standard (non-DST) timezone. If we can't get that, give up. */
155 if (!reg_query (hkey
, L
"StandardName", stdname
, sizeof stdname
,
156 "timezone information"))
162 /* Now open the timezone database registry key. Every subkey is a
163 timezone. The key name is what we're after, but to find the right
164 one, we have to compare the name of the previously fetched
165 "StandardName" with the "Std" value in the timezone info... */
166 hkey
= reg_open (HKEY_LOCAL_MACHINE
, REG_TZDB
, "timezone database");
169 for (idx
= 0; reg_enum (hkey
, idx
, keyname
, sizeof keyname
); ++idx
)
171 skey
= reg_open (hkey
, keyname
, NULL
);
174 /* ...however, on MUI-enabled machines, the names are not stored
175 directly in the above StandardName, rather it is a resource
176 pointer into tzres.dll. This is stored in MUI_Std.
177 Fortunately it's easy to recognize this situation: If
178 StandardName starts with @, it's a resource pointer, otherwise
179 it's the cleartext value. */
180 ret
= reg_query (skey
, stdname
[0] == L
'@' ? L
"MUI_Std" : L
"Std",
181 std2name
, sizeof std2name
, NULL
);
183 if (ret
&& !wcscmp (stdname
, std2name
))
190 /* We fetch the current Geo-location of the user and convert it to an
191 ISO 3166-1 compatible nation code. */
193 geo
= GetUserGeoID (GEOCLASS_NATION
);
194 if (geo
!= GEOID_NOT_AVAILABLE
)
195 GetGeoInfoW (geo
, GEO_ISO2
, country
, sizeof country
/ sizeof (*country
), 0);
196 /* If, for some reason, the Geo-location isn't available, we use the locale
199 GetLocaleInfoW (LOCALE_USER_DEFAULT
, LOCALE_SISO3166CTRYNAME
,
200 country
, sizeof country
);
202 /* Now iterate over the mapping table and find the right entry. */
203 for (idx
= 0; idx
< TZMAP_SIZE
; ++idx
)
205 if (!wcscasecmp (keyname
, tzmap
[idx
].win_tzkey
))
209 if (!wcscasecmp (country
, tzmap
[idx
].country
))
218 if (idx
>= TZMAP_SIZE
)
223 "%s: can't find matching POSIX timezone for "
224 "Windows timezone \"%ls\"\n",
225 program_invocation_short_name
, keyname
);
230 /* Got one. Print it.
231 Note: The tzmap array is in the R/O data section on x86_64. Don't
232 try to overwrite the space, as the code did originally. */
233 spc
= wcschr (tzmap
[idx
].posix_tzid
, L
' ');
235 spc
= wcschr (tzmap
[idx
].posix_tzid
, L
'\0');
236 printf ("%.*ls\n", (int) (spc
- tzmap
[idx
].posix_tzid
), tzmap
[idx
].posix_tzid
);