Discussion:
what shall we do with the drunken time_t ?
Bruno Haible
2017-04-24 22:40:18 UTC
Permalink
Hi Paul, all,

I'm trying to port utimens and futimens to native Windows. A major problem
is the semantic of time_t on native Windows. An experiment (at the end of this
mail) shows that:

* While on POSIX systems, time_t is the number of seconds since 1970-01-01
00:00:00 UTC/GMT [1][2], on native Windows, this is not the case. The
Microsoft doc is ambiguous [3]
"The time function returns the number of seconds elapsed since midnight
(00:00:00), January 1, 1970, Coordinated Universal Time (UTC), according
to the system clock."
What the MSVC library returns, is local time minus DST shift.
In other words, because of
local_time = utc_time + timezone + dst_shift
the time_t on native Windows is
utc_time + timezone

* On mingw, there is additionally a stat() bug. If you have DST today, mingw
behaves as if you have DST throughout the year.

* The gmtime function is useless: since time_t is (utc_time + timezone),
it produces a time string that represents (gmt_time + timezone) as well.

What can we do?

(a) Accept the non-POSIX definition of time_t, and tell programmers not
to use gmtime() when they want UTC time.

(b) Make time_t be like on POSIX, by overriding time(), stat(), fstat(),
and similar functions.

I'm in favour of (b), because
* Programs that need UTC usually do so because they exchange data with
other machines, and this messes up with the native Windows notion of time_t.
* POSIX also has APIs with 'struct timeval' (whose first element is a time_t),
and utime(), and they also become problematic when time_t is offset by a
timezone.

Opinions?

Bruno

[1] http://pubs.opengroup.org/onlinepubs/9699919799/functions/time.html
[2] http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_16
[3] https://msdn.microsoft.com/en-us/library/1f4c8f33.aspx


====================== Print st_mtime of an existing file ================
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main (int argc, char *argv[])
{
if (argc != 2) { fprintf (stderr, "Usage: stat-file FILE\n"); exit (1); }
const char *filename = argv[1];
struct stat buf;
if (stat (filename, &buf) < 0) { perror("stat"); exit(1); }
time_t tt = buf.st_mtime;
int day = tt / (24*3600);
int hour = (tt / 3600) % 24;
int seconds = tt % 3600;
fprintf (stdout, "mtime = %d %d %d\n", day, hour, seconds);
fprintf (stdout, "as GMT: %s\n", asctime (gmtime (&tt)));
fprintf (stdout, "as localtime: %s\n", asctime (localtime (&tt)));
}
================================ Results ===========================
(I am in CEST, i.e. GMT+1 with DST since end of March.)

A file last touched on 2016-11-27 18:32 GMT:
$ ls -l t.tar
-rw-r--r-- 1 bruno None 10240 Nov 27 19:32 t.tar

Cygwin:
mtime = 17132 18 1920
as GMT: Sun Nov 27 18:32:00 2016 (correct)
as localtime: Sun Nov 27 19:32:00 2016 (correct)

msvc:
mtime = 17132 19 1920
as GMT: Sun Nov 27 19:32:00 2016 (off by 1 h)
as localtime: Sun Nov 27 19:32:00 2016 (correct)

mingw:
mtime = 17132 20 1920
as GMT: Sun Nov 27 20:32:00 2016 (off by 2 h)
as localtime: Sun Nov 27 20:32:00 2016 (off by 1 h)

A file last touched on 2017-04-05 01:49 GMT:
$ ls -l n.txt
-rw-r--r-- 1 bruno None 93 Apr 5 03:49 n.txt

Cygwin:
mtime = 17261 1 2961
as GMT: Wed Apr 5 01:49:21 2017 (correct)
as localtime: Wed Apr 5 03:49:21 2017 (correct)

msvc:
mtime = 17261 2 2961
as GMT: Wed Apr 5 02:49:21 2017 (off by 1 h)
as localtime: Wed Apr 5 03:49:21 2017 (correct)

mingw:
mtime = 17261 2 2961
as GMT: Wed Apr 05 02:49:21 2017 (off by 1 h)
as localtime: Wed Apr 05 03:49:21 2017 (correct)
Paul Eggert
2017-04-25 07:21:36 UTC
Permalink
Couldn't asctime be causing the problem, insted of stat, gmtime etc.? The
Microsoft documentation for asctime says that it adjusts its output according to
local time zone settings, a behavior that disagrees with POSIX. This could
explain the symptoms you're seeing.

https://msdn.microsoft.com/en-us/library/kys1801b.aspx

Let's put it this way: Emacs ports to mingw and does not worry about gmtime
returning a value disagreeing with POSIX. And Emacs does not use asctime (it
uses strftime instead).
Bruno Haible
2017-04-25 19:09:18 UTC
Permalink
Hi Paul,
Post by Paul Eggert
Couldn't asctime be causing the problem, insted of stat, gmtime etc.?
No, the problem is really with stat(). In my test program I had decomposed
the time_t into days, hours, seconds myself:
int day = tt / (24*3600);
int hour = (tt / 3600) % 24;
int seconds = tt % 3600;
fprintf (stdout, "mtime = %d %d %d\n", day, hour, seconds);

And the time() function appears to follow the POSIX semantics. New test program:
====================================================================
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

static void show_time (const char *label, time_t tt)
{
int day = tt / (24*3600);
int hour = (tt / 3600) % 24;
int seconds = tt % 3600;
fprintf (stdout, "%s = %d %d %d\n", label, day, hour, seconds);
fprintf (stdout, " as GMT: %s", asctime (gmtime (&tt)));
fprintf (stdout, " as localtime: %s", asctime (localtime (&tt)));
}

int main (int argc, char *argv[])
{
int i;
{
show_time ("now", time(NULL));
}
for (i = 1; i < argc; i++) {
const char *filename = argv[i];
struct stat buf;
if (stat (filename, &buf) < 0) { perror("stat"); exit(1); }
fprintf (stdout, "File %s ", filename);
show_time ("mtime", buf.st_mtime);
}
}
====================================================================

Output without the asctime lines:


German timezone (= GMT + 1h + dst, DST currently enabled):

$ ./stat-file-cygwin.exe n.txt t.tar
now = 17281 16 1665
File n.txt mtime = 17261 1 2961
File t.tar mtime = 17132 18 1920

$ ./stat-file-msvc.exe n.txt t.tar
now = 17281 16 1700
File n.txt mtime = 17261 2 2961
File t.tar mtime = 17132 19 1920

$ ./stat-file-mingw.exe n.txt t.tar
now = 17281 16 1781
File n.txt mtime = 17261 2 2961
File t.tar mtime = 17132 20 1920

Israel timezone (= GMT + 2h + dst, DST currently enabled):

$ ./stat-file-cygwin.exe n.txt t.tar
now = 17281 16 2356
File n.txt mtime = 17261 1 2961
File t.tar mtime = 17132 18 1920

$ ./stat-file-msvc.exe n.txt t.tar
now = 17281 16 2358
File n.txt mtime = 17261 3 2961
File t.tar mtime = 17132 20 1920

$ ./stat-file-mingw.exe n.txt t.tar
now = 17281 16 2359
File n.txt mtime = 17261 3 2961
File t.tar mtime = 17132 21 1920

You can see that:
* The time_t values from time(NULL) are not affected by the time zone.
* The time_t values from stat() are affected by the time zone in MSVC and mingw.
The offset is
- for MSVC: timezone offset (1 h in German timezone, 2 h in Israel timezone)
- for mingw: buggy (1 h or 2 h in German timezone, 2 h or 3 h in Israel
timezone, depending on the file)

By the way, as soon as I change the timezone from German timezone to Israel
timezone, the Notepad++ editor tells me that all files have changed on disk and
asks whether I want to reload them (one by one!). This is really a symptom of
the stat() bug.
Post by Paul Eggert
Let's put it this way: Emacs ports to mingw and does not worry about gmtime
returning a value disagreeing with POSIX. And Emacs does not use asctime (it
uses strftime instead).
Emacs also appears to not use st_mtime of opened files (at least not by default).
But if it warned the user about files modified in the background (like Nodepad++
and KDE kate do), it would exhibit the same symptom.

Bruno
Paul Eggert
2017-04-25 22:37:51 UTC
Permalink
Post by Bruno Haible
Emacs also appears to not use st_mtime of opened files (at least not by default).
But if it warned the user about files modified in the background (like Nodepad++
and KDE kate do), it would exhibit the same symptom.
Your latest message prompted me to search microsoft.com further, and I
found this:

https://support.microsoft.com/en-us/help/190315

which says that the glitchy behavior is "by design" (!) and occurs in
NTFS but not FAT (!!).

I wonder whether Cygwin deals with this problem?

Perhaps we should just advise NTFS users to not click the box saying
"Automatically adjust clock for daylight saving changes" in their
MS-Windows preferences.
Bruno Haible
2017-04-26 14:35:17 UTC
Permalink
Hi Paul,
Post by Paul Eggert
Your latest message prompted me to search microsoft.com further, and I
https://support.microsoft.com/en-us/help/190315
Excellent finding! Thank you.
Post by Paul Eggert
I wonder whether Cygwin deals with this problem?
Cygwin's stat() implementation converts the times (from FILETIME to time_t)
in a very simple way: by adding the number of seconds from 1601-01-01 to
1970-01-01.

Whereas the MSVC stat() apparently does computations that depend on the
time zone.
Post by Paul Eggert
which says that the glitchy behavior is "by design" (!)
This "design" is probably the backward compatibility with the old times when
1. most computers were not connected to the Internet, and thus could not
connect to an NTP server,
2. the OS did not contain the time zone and DST dates database, thus it was
the responsibility of the user to modify the time zone twice a year.
Post by Paul Eggert
and occurs in NTFS but not FAT (!!).
This is not true (anymore?). The effect exists also on FAT32, just like on NTFS.
Here's the experiment on NTFS:

$ ./stat-file-cygwin.exe n.txt t.tar
File n.txt mtime = 17261 1 2961
File t.tar mtime = 17132 18 1920

$ ./stat-file-msvc.exe n.txt t.tar
File n.txt mtime = 17261 2 2961
File t.tar mtime = 17132 19 1920

$ ./stat-file-mingw.exe n.txt t.tar
File n.txt mtime = 17261 2 2961
File t.tar mtime = 17132 20 1920

and on FAT32:

$ ~/stat-file-cygwin.exe n.txt t.tar
File n.txt mtime = 17261 1 2962
File t.tar mtime = 17132 18 1922

$ ~/stat-file-msvc.exe n.txt t.tar
File n.txt mtime = 17261 2 2962
File t.tar mtime = 17132 19 1922

$ ~/stat-file-mingw.exe n.txt t.tar
File n.txt mtime = 17261 2 2962
File t.tar mtime = 17132 20 1922

In both cases, with the notation local_time = utc_time + zone_offset + dst,
you have
- in Cygwin: mtime = utc_time
- in MSVC: mtime = utc_time + zone_offset
- in mingw: mtime = utc_time + zone_offset + dst_now - dst
Post by Paul Eggert
Perhaps we should just advise NTFS users to not click the box saying
"Automatically adjust clock for daylight saving changes" in their
MS-Windows preferences.
I don't think this is a good advice because it means that
* Either the user's time display ignores DST (which is desirable only for
some farmers opposed to DST),
or it's the user's responsibility to change the time zone twice a year
(which most users will find annoying nowadays).
* The bug is still not fixed. Here's the experiment with
"Adjust for daylight saving time automatically" disabled:

In German timezone:

$ ./stat-file-cygwin.exe n.txt t.tar
File n.txt mtime = 17261 1 2961
File t.tar mtime = 17132 18 1920

$ ./stat-file-msvc.exe n.txt t.tar
File n.txt mtime = 17261 1 2961
File t.tar mtime = 17132 19 1920

$ ./stat-file-mingw.exe n.txt t.tar
File n.txt mtime = 17261 1 2961
File t.tar mtime = 17132 19 1920

In Israel timezone:

$ ./stat-file-cygwin.exe n.txt t.tar
File n.txt mtime = 17261 1 2961
File t.tar mtime = 17132 18 1920

$ ./stat-file-msvc.exe n.txt t.tar
File n.txt mtime = 17261 2 2961
File t.tar mtime = 17132 20 1920

$ ./stat-file-mingw.exe n.txt t.tar
File n.txt mtime = 17261 2 2961
File t.tar mtime = 17132 20 1920

So, in this case, with the notation local_time = utc_time + zone_offset + dst,
you have
- in Cygwin: mtime = utc_time
- in MSVC: mtime = utc_time + zone_offset - dst
- in mingw: mtime = utc_time + zone_offset - dst


I think, if we want a sane behaviour, we have no choice than to override stat()
and fstat().

Bruno
Paul Eggert
2017-04-27 06:47:28 UTC
Permalink
Post by Bruno Haible
if we want a sane behaviour, we have no choice than to override stat()
and fstat()
What a pain. Would it be limited to just those two? For example, is there a
system call like utimensat that lets you set a file's timestamps?
Bruno Haible
2017-04-27 10:18:18 UTC
Permalink
Hi Paul,
Post by Paul Eggert
Post by Bruno Haible
if we want a sane behaviour, we have no choice than to override stat()
and fstat()
What a pain. Would it be limited to just those two? For example, is there a
system call like utimensat that lets you set a file's timestamps?
It's the *utimens* test failures that brought us here. [1][2]
I don't think the time_t trouble goes beyond that.

Bruno

[1] https://lists.gnu.org/archive/html/bug-gnulib/2016-12/msg00112.html
[2] https://lists.gnu.org/archive/html/bug-gnulib/2017-04/msg00010.html
Bruno Haible
2017-04-29 20:36:18 UTC
Permalink
Post by Bruno Haible
================================ Results ===========================
(I am in CEST, i.e. GMT+1 with DST since end of March.)
$ ls -l t.tar
-rw-r--r-- 1 bruno None 10240 Nov 27 19:32 t.tar
mtime = 17132 18 1920
as GMT: Sun Nov 27 18:32:00 2016 (correct)
as localtime: Sun Nov 27 19:32:00 2016 (correct)
mtime = 17132 19 1920
as GMT: Sun Nov 27 19:32:00 2016 (off by 1 h)
as localtime: Sun Nov 27 19:32:00 2016 (correct)
mtime = 17132 20 1920
as GMT: Sun Nov 27 20:32:00 2016 (off by 2 h)
as localtime: Sun Nov 27 20:32:00 2016 (off by 1 h)
$ ls -l n.txt
-rw-r--r-- 1 bruno None 93 Apr 5 03:49 n.txt
mtime = 17261 1 2961
as GMT: Wed Apr 5 01:49:21 2017 (correct)
as localtime: Wed Apr 5 03:49:21 2017 (correct)
mtime = 17261 2 2961
as GMT: Wed Apr 5 02:49:21 2017 (off by 1 h)
as localtime: Wed Apr 5 03:49:21 2017 (correct)
mtime = 17261 2 2961
as GMT: Wed Apr 05 02:49:21 2017 (off by 1 h)
as localtime: Wed Apr 05 03:49:21 2017 (correct)
Part of the confusion came from the facts that
- I'm using Cygwin as development environment,
- Cygwin defines the environment variable TZ, with value "Europe/Berlin"
in my case [1],
- the Microsoft CRT interprets TZ, but with a different expected syntax [2][3],
which led to tzname[0] = "Eur", tzname[1] = "ope".

The problem is that an absent or empty TZ value means "GMT" for Cygwin,
whereas it means the timezone from the Windows Control Panel in native Windows.
There have been proposals that Cygwin should unset TZ when it invokes a native
Windows program [4][5], but it has not been implemented so far.

Here are updated results (the msvc and mingw programs run with empty TZ):
===============================================================================

With DST:
$ ls -l n.txt
-rw-r--r-- 1 bruno None 93 Apr 5 03:49 n.txt

Cygwin:
File n.txt mtime = 17261 1 2961 (correct)
as GMT: Wed Apr 5 01:49:21 2017 (correct)
as localtime: Wed Apr 5 03:49:21 2017 (correct)

msvc with empty TZ:
File n.txt mtime = 17261 1 2961 (correct)
as GMT: Wed Apr 5 01:49:21 2017 (correct)
as localtime: Wed Apr 5 03:49:21 2017 (correct)
msvc with Cygwin's TZ:
File n.txt mtime = 17261 2 2961 (off by 1 h)
as GMT: Wed Apr 5 02:49:21 2017 (off by 1 h)
as localtime: Wed Apr 5 03:49:21 2017 (correct)

mingw with empty TZ:
File n.txt mtime = 17261 1 2961 (correct)
as GMT: Wed Apr 05 01:49:21 2017 (correct)
as localtime: Wed Apr 05 03:49:21 2017 (correct)
mingw with Cygwin's TZ:
File n.txt mtime = 17261 2 2961 (off by 1 h)
as GMT: Wed Apr 05 02:49:21 2017 (off by 1 h)
as localtime: Wed Apr 05 03:49:21 2017 (correct)

Without DST:
$ ls -l t.tar
-rw-r--r-- 1 bruno None 10240 Nov 27 19:32 t.tar

Cygwin:
File t.tar mtime = 17132 18 1920 (correct)
as GMT: Sun Nov 27 18:32:00 2016 (correct)
as localtime: Sun Nov 27 19:32:00 2016 (correct)

msvc with empty TZ:
File t.tar mtime = 17132 18 1920 (correct)
as GMT: Sun Nov 27 18:32:00 2016 (correct)
as localtime: Sun Nov 27 19:32:00 2016 (correct)
msvc with Cygwin's TZ:
File t.tar mtime = 17132 19 1920 (off by 1 h)
as GMT: Sun Nov 27 19:32:00 2016 (off by 1 h)
as localtime: Sun Nov 27 19:32:00 2016 (correct)

mingw with empty TZ:
File t.tar mtime = 17132 19 1920 (off by 1 h)
as GMT: Sun Nov 27 19:32:00 2016 (off by 1 h)
as localtime: Sun Nov 27 20:32:00 2016 (off by 1 h)
mingw with Cygwin's TZ:
File t.tar mtime = 17132 20 1920 (off by 2 h)
as GMT: Sun Nov 27 20:32:00 2016 (off by 2 h)
as localtime: Sun Nov 27 20:32:00 2016 (off by 1 h)

===============================================================================

I'm still going to override stat() and fstat(), because
- There are the unlucky Cygwin users like me.
- Doing time zone sensitive computations for time_t is madness, and leads
to the Notepad++ notifications when the user changes the time zone.
- These rewrites of stat() and fstat() are the basis for future enhancements
(struct timeval instead of time_t -> higher timestamp precisions,
dev_t and ino_t, and symbolic links).

Bruno

[1] https://stackoverflow.com/questions/40350812/cygwin-shows-utc-time-instead-of-local-time
[2] https://msdn.microsoft.com/en-us/library/aa273389.aspx
[3] https://msdn.microsoft.com/en-us/library/90s5c885.aspx
[4] https://cygwin.com/ml/cygwin/2012-03/msg00048.html
[5] https://cygwin.com/ml/cygwin-patches/2012-q2/msg00007.html
Bruno Haible
2017-04-30 08:07:58 UTC
Permalink
Post by Bruno Haible
- the Microsoft CRT interprets TZ, but with a different expected syntax [2][3],
which led to tzname[0] = "Eur", tzname[1] = "ope".
The TZ environment variable affects a lot of C library functions:

* Some which should not be exhibiting locale dependent behaviour at all:

fstat, _fstat*
https://msdn.microsoft.com/en-us/library/221w8e43.aspx
http://pubs.opengroup.org/onlinepubs/9699919799/functions/fstat.html

stat, _stat*, _wstat*
https://msdn.microsoft.com/en-us/library/14h5k7ff.aspx
http://pubs.opengroup.org/onlinepubs/9699919799/functions/stat.html

_findfirst*, _wfindfirst*
https://msdn.microsoft.com/en-us/library/zyzxfzac.aspx
https://msdn.microsoft.com/en-us/library/kda16keh.aspx

utime, _utime*, _wutime*
https://msdn.microsoft.com/en-us/library/4wacf567.aspx
http://pubs.opengroup.org/onlinepubs/9699919799/functions/utime.html

_futime*
https://msdn.microsoft.com/en-us/library/8592kht8.aspx

* Some which should obey TZ, just that they should ignore the values set by
Cygwin (instead of exhibiting garbage behaviour):

_ftime*
https://msdn.microsoft.com/en-us/library/z54t9z5f.aspx
http://man7.org/linux/man-pages/man3/ftime.3.html

localtime, _localtime*
https://msdn.microsoft.com/en-us/library/bf12f0hc.aspx
https://msdn.microsoft.com/en-us/library/a442x3ye.aspx
http://pubs.opengroup.org/onlinepubs/9699919799/functions/localtime.html

_tzset
https://msdn.microsoft.com/en-us/library/aa273389.aspx
https://msdn.microsoft.com/en-us/library/90s5c885.aspx
http://pubs.opengroup.org/onlinepubs/9699919799/functions/tzset.html

mktime, _mktime*
https://msdn.microsoft.com/en-us/library/d1y53h2a.aspx
http://pubs.opengroup.org/onlinepubs/9699919799/functions/mktime.html

ctime, _ctime*, _wctime*
https://msdn.microsoft.com/en-us/library/59w5xcdy.aspx
http://pubs.opengroup.org/onlinepubs/9699919799/functions/ctime.html

strftime, _strftime_l
https://msdn.microsoft.com/en-us/library/fe06s4ak.aspx
http://pubs.opengroup.org/onlinepubs/9699919799/functions/strftime.html

wcsftime, _wcsftime_l
https://msdn.microsoft.com/en-us/library/fe06s4ak.aspx
http://pubs.opengroup.org/onlinepubs/9699919799/functions/wcsftime.html

Bruno
Bruno Haible
2017-04-30 17:39:34 UTC
Permalink
As a first step, let me document this for the POSIX functions.


2017-04-30 Bruno Haible <***@clisp.org>

Document the problem with the Cygwin environment variable TZ.
* doc/posix-functions/tzset.texi: Add note about TZ.
* doc/posix-functions/ctime.texi: Likewise.
* doc/posix-functions/localtime.texi: Likewise.
* doc/posix-functions/mktime.texi: Likewise.
* doc/posix-functions/strftime.texi: Likewise.
* doc/posix-functions/wcsftime.texi: Likewise.
* doc/pastposix-functions/ftime.texi: Likewise.

diff --git a/doc/pastposix-functions/ftime.texi b/doc/pastposix-functions/ftime.texi
index 0fcaed1..582a088 100644
--- a/doc/pastposix-functions/ftime.texi
+++ b/doc/pastposix-functions/ftime.texi
@@ -16,6 +16,9 @@ Portability problems not fixed by Gnulib:
This function is missing on some platforms:
Mac OS X 10.5, FreeBSD 6.0, NetBSD 5.0, OpenBSD 3.8, IRIX 5.3, Solaris 2.4.
@item
+On native Windows platforms (mingw, MSVC), this function works incorrectly
+when the environment variable @code{TZ} has been set by Cygwin.
+@item
This function is marked as ``legacy'' in POSIX. Better use @code{gettimeofday}
or @code{clock_gettime} instead, and use @code{ftime} only as a fallback for
portability to Windows platforms.
diff --git a/doc/posix-functions/ctime.texi b/doc/posix-functions/ctime.texi
index f5a7c27..e54a7b5 100644
--- a/doc/posix-functions/ctime.texi
+++ b/doc/posix-functions/ctime.texi
@@ -13,6 +13,9 @@ Portability problems fixed by Gnulib:
Portability problems not fixed by Gnulib:
@itemize
@item
+On native Windows platforms (mingw, MSVC), this function works incorrectly
+when the environment variable @code{TZ} has been set by Cygwin.
+@item
This function may overflow its internal buffer if an invalid year is passed.
@item
The @code{ctime} function need not be reentrant, and consequently is
diff --git a/doc/posix-functions/localtime.texi b/doc/posix-functions/localtime.texi
index 49a6ddd..1d6acdb 100644
--- a/doc/posix-functions/localtime.texi
+++ b/doc/posix-functions/localtime.texi
@@ -12,7 +12,11 @@ Portability problems fixed by Gnulib:

Portability problems not fixed by Gnulib:
@itemize
-@item On some platforms, this function returns nonsense values for
+@item
+On native Windows platforms (mingw, MSVC), this function works incorrectly
+when the environment variable @code{TZ} has been set by Cygwin.
+@item
+On some platforms, this function returns nonsense values for
unsupported arguments (like @math{2^56}), rather than failing:
FreeBSD 10.
@end itemize
diff --git a/doc/posix-functions/mktime.texi b/doc/posix-functions/mktime.texi
index 9756948..ffb7b79 100644
--- a/doc/posix-functions/mktime.texi
+++ b/doc/posix-functions/mktime.texi
@@ -16,4 +16,7 @@ Portability problems fixed by Gnulib:

Portability problems not fixed by Gnulib:
@itemize
+@item
+On native Windows platforms (mingw, MSVC), this function works incorrectly
+when the environment variable @code{TZ} has been set by Cygwin.
@end itemize
diff --git a/doc/posix-functions/strftime.texi b/doc/posix-functions/strftime.texi
index e58e1ef..d371818 100644
--- a/doc/posix-functions/strftime.texi
+++ b/doc/posix-functions/strftime.texi
@@ -13,6 +13,9 @@ Portability problems fixed by Gnulib:
Portability problems not fixed by Gnulib:
@itemize
@item
+On native Windows platforms (mingw, MSVC), this function works incorrectly
+when the environment variable @code{TZ} has been set by Cygwin.
+@item
The Windows C runtime library (which is used by MinGW) does not
support the %e specifier (and possibly the other more recent SUS
specifiers too, i.e., %C, %D, %h, %n, %r, %R, %t, and %T).
diff --git a/doc/posix-functions/tzset.texi b/doc/posix-functions/tzset.texi
index 35fc509..30b147c 100644
--- a/doc/posix-functions/tzset.texi
+++ b/doc/posix-functions/tzset.texi
@@ -16,4 +16,7 @@ Solaris 2.6.

Portability problems not fixed by Gnulib:
@itemize
+@item
+On native Windows platforms (mingw, MSVC), this function works incorrectly
+when the environment variable @code{TZ} has been set by Cygwin.
@end itemize
diff --git a/doc/posix-functions/wcsftime.texi b/doc/posix-functions/wcsftime.texi
index 0faa211..8ab82c2 100644
--- a/doc/posix-functions/wcsftime.texi
+++ b/doc/posix-functions/wcsftime.texi
@@ -16,6 +16,9 @@ Portability problems not fixed by Gnulib:
This function is missing on some platforms:
OpenBSD 3.8, Minix 3.1.8, IRIX 5.3, Solaris 2.5.1, Cygwin 1.5.x, BeOS.
@item
+On native Windows platforms (mingw, MSVC), this function works incorrectly
+when the environment variable @code{TZ} has been set by Cygwin.
+@item
On AIX and Windows platforms, @code{wchar_t} is a 16-bit type and therefore cannot
accommodate all Unicode characters.
@end itemize
Bruno Haible
2017-04-30 17:46:18 UTC
Permalink
Post by Bruno Haible
* Some which should obey TZ, just that they should ignore the values set by
ctime, _ctime*, _wctime*
https://msdn.microsoft.com/en-us/library/59w5xcdy.aspx
http://pubs.opengroup.org/onlinepubs/9699919799/functions/ctime.html
This patch implements it.


2017-04-30 Bruno Haible <***@clisp.org>

ctime: New module.
* lib/time.in.h (ctime): New declaration.
* lib/ctime.c: New file.
* m4/ctime.m4: New file.
* m4/time_h.m4 (gl_HEADER_TIME_H_DEFAULTS): Initialize GNULIB_CTIME,
REPLACE_CTIME.
* modules/time (Makefile.am): Substitute GNULIB_CTIME, REPLACE_CTIME.
* modules/ctime: New file.
* doc/posix-functions/ctime.texi: Mention the new module.

diff --git a/doc/posix-functions/ctime.texi b/doc/posix-functions/ctime.texi
index e54a7b5..6abc4c4 100644
--- a/doc/posix-functions/ctime.texi
+++ b/doc/posix-functions/ctime.texi
@@ -4,18 +4,18 @@

POSIX specification:@* @url{http://www.opengroup.org/onlinepubs/9699919799/functions/ctime.html}

-Gnulib module: ---
+Gnulib module: ctime

Portability problems fixed by Gnulib:
@itemize
+@item
+On native Windows platforms (mingw, MSVC), this function works incorrectly
+when the environment variable @code{TZ} has been set by Cygwin.
@end itemize

Portability problems not fixed by Gnulib:
@itemize
@item
-On native Windows platforms (mingw, MSVC), this function works incorrectly
-when the environment variable @code{TZ} has been set by Cygwin.
-@item
This function may overflow its internal buffer if an invalid year is passed.
@item
The @code{ctime} function need not be reentrant, and consequently is
diff --git a/lib/ctime.c b/lib/ctime.c
new file mode 100644
index 0000000..16416ab
--- /dev/null
+++ b/lib/ctime.c
@@ -0,0 +1,40 @@
+/* Work around platform bugs in ctime.
+ Copyright (C) 2017 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+/* Specification. */
+#include <time.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#undef ctime
+
+char *
+rpl_ctime (const time_t *tp)
+{
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+ /* If the environment variable TZ has been set by Cygwin, neutralize it.
+ The Microsoft CRT interprets TZ differently than Cygwin and produces
+ incorrect results if TZ has the syntax used by Cygwin. */
+ const char *tz = getenv ("TZ");
+ if (tz != NULL && strchr (tz, '/') != NULL)
+ _putenv ("TZ=");
+#endif
+
+ return ctime (tp);
+}
diff --git a/lib/time.in.h b/lib/time.in.h
index 86436c2..47087d8 100644
--- a/lib/time.in.h
+++ b/lib/time.in.h
@@ -233,6 +233,22 @@ _GL_CXXALIAS_SYS (strptime, char *, (char const *restrict __buf,
_GL_CXXALIASWARN (strptime);
# endif

+/* Convert *TP to a date and time string. See
+ <http://pubs.opengroup.org/onlinepubs/9699919799/functions/ctime.html>. */
+# if @GNULIB_CTIME@
+# if @REPLACE_CTIME@
+# if !(defined __cplusplus && defined GNULIB_NAMESPACE)
+# define ctime rpl_ctime
+# endif
+_GL_FUNCDECL_RPL (ctime, char *, (time_t const *__tp)
+ _GL_ARG_NONNULL ((1)));
+_GL_CXXALIAS_RPL (ctime, char *, (time_t const *__tp));
+# else
+_GL_CXXALIAS_SYS (ctime, char *, (time_t const *__tp));
+# endif
+_GL_CXXALIASWARN (ctime);
+# endif
+
# if defined _GNU_SOURCE && @GNULIB_TIME_RZ@ && ! @HAVE_TIMEZONE_T@
typedef struct tm_zone *timezone_t;
_GL_FUNCDECL_SYS (tzalloc, timezone_t, (char const *__name));
diff --git a/m4/ctime.m4 b/m4/ctime.m4
new file mode 100644
index 0000000..2d4b3f8
--- /dev/null
+++ b/m4/ctime.m4
@@ -0,0 +1,15 @@
+# ctime.m4 serial 1
+dnl Copyright (C) 2017 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_CTIME],
+[
+ AC_REQUIRE([gl_HEADER_TIME_H_DEFAULTS])
+ AC_REQUIRE([AC_CANONICAL_HOST])
+ case "$host_os" in
+ mingw*) REPLACE_CTIME=1 ;;
+ *) REPLACE_CTIME=0 ;;
+ esac
+])
diff --git a/m4/time_h.m4 b/m4/time_h.m4
index b925678..fdd819b 100644
--- a/m4/time_h.m4
+++ b/m4/time_h.m4
@@ -2,7 +2,7 @@

# Copyright (C) 2000-2001, 2003-2007, 2009-2017 Free Software Foundation, Inc.

-# serial 9
+# serial 10

# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -104,6 +104,7 @@ AC_DEFUN([gl_TIME_MODULE_INDICATOR],

AC_DEFUN([gl_HEADER_TIME_H_DEFAULTS],
[
+ GNULIB_CTIME=0; AC_SUBST([GNULIB_CTIME])
GNULIB_MKTIME=0; AC_SUBST([GNULIB_MKTIME])
GNULIB_NANOSLEEP=0; AC_SUBST([GNULIB_NANOSLEEP])
GNULIB_STRPTIME=0; AC_SUBST([GNULIB_STRPTIME])
@@ -118,6 +119,7 @@ AC_DEFUN([gl_HEADER_TIME_H_DEFAULTS],
dnl If another module says to replace or to not replace, do that.
dnl Otherwise, replace only if someone compiles with -DGNULIB_PORTCHECK;
dnl this lets maintainers check for portability.
+ REPLACE_CTIME=GNULIB_PORTCHECK; AC_SUBST([REPLACE_CTIME])
REPLACE_LOCALTIME_R=GNULIB_PORTCHECK; AC_SUBST([REPLACE_LOCALTIME_R])
REPLACE_MKTIME=GNULIB_PORTCHECK; AC_SUBST([REPLACE_MKTIME])
REPLACE_NANOSLEEP=GNULIB_PORTCHECK; AC_SUBST([REPLACE_NANOSLEEP])
diff --git a/modules/ctime b/modules/ctime
new file mode 100644
index 0000000..d700ca8
--- /dev/null
+++ b/modules/ctime
@@ -0,0 +1,27 @@
+Description:
+ctime() function: convert time to string.
+
+Files:
+lib/ctime.c
+m4/ctime.m4
+
+Depends-on:
+time
+
+configure.ac:
+gl_FUNC_CTIME
+if test $REPLACE_CTIME = 1; then
+ AC_LIBOBJ([ctime])
+fi
+gl_TIME_MODULE_INDICATOR([ctime])
+
+Makefile.am:
+
+Include:
+<time.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all
diff --git a/modules/time b/modules/time
index d12add0..eff6132 100644
--- a/modules/time
+++ b/modules/time
@@ -30,6 +30,7 @@ time.h: time.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H) $(
-e 's|@''PRAGMA_SYSTEM_HEADER''@|@PRAGMA_SYSTEM_HEADER@|g' \
-e 's|@''PRAGMA_COLUMNS''@|@PRAGMA_COLUMNS@|g' \
-e 's|@''NEXT_TIME_H''@|$(NEXT_TIME_H)|g' \
+ -e 's/@''GNULIB_CTIME''@/$(GNULIB_CTIME)/g' \
-e 's/@''GNULIB_GETTIMEOFDAY''@/$(GNULIB_GETTIMEOFDAY)/g' \
-e 's/@''GNULIB_MKTIME''@/$(GNULIB_MKTIME)/g' \
-e 's/@''GNULIB_NANOSLEEP''@/$(GNULIB_NANOSLEEP)/g' \
@@ -42,6 +43,7 @@ time.h: time.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H) $(
-e 's|@''HAVE_STRPTIME''@|$(HAVE_STRPTIME)|g' \
-e 's|@''HAVE_TIMEGM''@|$(HAVE_TIMEGM)|g' \
-e 's|@''HAVE_TIMEZONE_T''@|$(HAVE_TIMEZONE_T)|g' \
+ -e 's|@''REPLACE_CTIME''@|$(REPLACE_CTIME)|g' \
-e 's|@''REPLACE_GMTIME''@|$(REPLACE_GMTIME)|g' \
-e 's|@''REPLACE_LOCALTIME''@|$(REPLACE_LOCALTIME)|g' \
-e 's|@''REPLACE_LOCALTIME_R''@|$(REPLACE_LOCALTIME_R)|g' \
Bruno Haible
2017-04-30 17:47:23 UTC
Permalink
Post by Bruno Haible
* Some which should obey TZ, just that they should ignore the values set by
localtime, _localtime*
https://msdn.microsoft.com/en-us/library/bf12f0hc.aspx
https://msdn.microsoft.com/en-us/library/a442x3ye.aspx
http://pubs.opengroup.org/onlinepubs/9699919799/functions/localtime.html
This patch does it.


2017-04-30 Bruno Haible <***@clisp.org>

localtime: New module.
* lib/time.in.h (localtime): Declare also if requested by module
'localtime'.
* lib/localtime.c: New file.
* m4/localtime.m4: New file.
* m4/time_h.m4 (gl_HEADER_TIME_H_DEFAULTS): Initialize GNULIB_LOCALTIME.
* modules/time (Makefile.am): Substitute GNULIB_LOCALTIME.
* modules/localtime: New file.
* doc/posix-functions/localtime.texi: Mention the new module.

diff --git a/doc/posix-functions/localtime.texi b/doc/posix-functions/localtime.texi
index 1d6acdb..74d4a6a 100644
--- a/doc/posix-functions/localtime.texi
+++ b/doc/posix-functions/localtime.texi
@@ -4,18 +4,18 @@

POSIX specification:@* @url{http://www.opengroup.org/onlinepubs/9699919799/functions/localtime.html}

-Gnulib module: ---
+Gnulib module: localtime

Portability problems fixed by Gnulib:
@itemize
+@item
+On native Windows platforms (mingw, MSVC), this function works incorrectly
+when the environment variable @code{TZ} has been set by Cygwin.
@end itemize

Portability problems not fixed by Gnulib:
@itemize
@item
-On native Windows platforms (mingw, MSVC), this function works incorrectly
-when the environment variable @code{TZ} has been set by Cygwin.
-@item
On some platforms, this function returns nonsense values for
unsupported arguments (like @math{2^56}), rather than failing:
FreeBSD 10.
diff --git a/lib/localtime.c b/lib/localtime.c
new file mode 100644
index 0000000..07b532a
--- /dev/null
+++ b/lib/localtime.c
@@ -0,0 +1,45 @@
+/* Work around platform bugs in localtime.
+ Copyright (C) 2017 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+/* Specification. */
+#include <time.h>
+
+/* Keep consistent with gettimeofday.c! */
+#if !(GETTIMEOFDAY_CLOBBERS_LOCALTIME || TZSET_CLOBBERS_LOCALTIME)
+
+# include <stdlib.h>
+# include <string.h>
+
+# undef localtime
+
+struct tm *
+rpl_localtime (const time_t *tp)
+{
+# if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+ /* If the environment variable TZ has been set by Cygwin, neutralize it.
+ The Microsoft CRT interprets TZ differently than Cygwin and produces
+ incorrect results if TZ has the syntax used by Cygwin. */
+ const char *tz = getenv ("TZ");
+ if (tz != NULL && strchr (tz, '/') != NULL)
+ _putenv ("TZ=");
+# endif
+
+ return localtime (tp);
+}
+
+#endif
diff --git a/lib/time.in.h b/lib/time.in.h
index 47087d8..2587cdd 100644
--- a/lib/time.in.h
+++ b/lib/time.in.h
@@ -187,7 +187,7 @@ _GL_CXXALIASWARN (gmtime_r);
/* Convert TIMER to RESULT, assuming local time and UTC respectively. See
<http://www.opengroup.org/susv3xsh/localtime.html> and
<http://www.opengroup.org/susv3xsh/gmtime.html>. */
-# if @GNULIB_GETTIMEOFDAY@
+# if @GNULIB_LOCALTIME@ || @GNULIB_GETTIMEOFDAY@
# if @REPLACE_LOCALTIME@
# if !(defined __cplusplus && defined GNULIB_NAMESPACE)
# undef localtime
diff --git a/m4/localtime.m4 b/m4/localtime.m4
new file mode 100644
index 0000000..deb54a3
--- /dev/null
+++ b/m4/localtime.m4
@@ -0,0 +1,14 @@
+# localtime.m4 serial 1
+dnl Copyright (C) 2017 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_LOCALTIME],
+[
+ AC_REQUIRE([gl_HEADER_TIME_H_DEFAULTS])
+ AC_REQUIRE([AC_CANONICAL_HOST])
+ case "$host_os" in
+ mingw*) REPLACE_LOCALTIME=1 ;;
+ esac
+])
diff --git a/m4/time_h.m4 b/m4/time_h.m4
index fdd819b..2eaf3ae 100644
--- a/m4/time_h.m4
+++ b/m4/time_h.m4
@@ -106,6 +106,7 @@ AC_DEFUN([gl_HEADER_TIME_H_DEFAULTS],
[
GNULIB_CTIME=0; AC_SUBST([GNULIB_CTIME])
GNULIB_MKTIME=0; AC_SUBST([GNULIB_MKTIME])
+ GNULIB_LOCALTIME=0; AC_SUBST([GNULIB_LOCALTIME])
GNULIB_NANOSLEEP=0; AC_SUBST([GNULIB_NANOSLEEP])
GNULIB_STRPTIME=0; AC_SUBST([GNULIB_STRPTIME])
GNULIB_TIMEGM=0; AC_SUBST([GNULIB_TIMEGM])
diff --git a/modules/localtime b/modules/localtime
new file mode 100644
index 0000000..5da46a9
--- /dev/null
+++ b/modules/localtime
@@ -0,0 +1,27 @@
+Description:
+localtime() function: convert time to broken-down local time.
+
+Files:
+lib/localtime.c
+m4/localtime.m4
+
+Depends-on:
+time
+
+configure.ac:
+gl_FUNC_LOCALTIME
+if test $REPLACE_LOCALTIME = 1; then
+ AC_LIBOBJ([localtime])
+fi
+gl_TIME_MODULE_INDICATOR([localtime])
+
+Makefile.am:
+
+Include:
+<time.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all
diff --git a/modules/time b/modules/time
index eff6132..4bbfbd4 100644
--- a/modules/time
+++ b/modules/time
@@ -32,6 +32,7 @@ time.h: time.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H) $(
-e 's|@''NEXT_TIME_H''@|$(NEXT_TIME_H)|g' \
-e 's/@''GNULIB_CTIME''@/$(GNULIB_CTIME)/g' \
-e 's/@''GNULIB_GETTIMEOFDAY''@/$(GNULIB_GETTIMEOFDAY)/g' \
+ -e 's/@''GNULIB_LOCALTIME''@/$(GNULIB_LOCALTIME)/g' \
-e 's/@''GNULIB_MKTIME''@/$(GNULIB_MKTIME)/g' \
-e 's/@''GNULIB_NANOSLEEP''@/$(GNULIB_NANOSLEEP)/g' \
-e 's/@''GNULIB_STRPTIME''@/$(GNULIB_STRPTIME)/g' \
Bruno Haible
2017-04-30 17:51:24 UTC
Permalink
Post by Bruno Haible
* Some which should obey TZ, just that they should ignore the values set by
mktime, _mktime*
https://msdn.microsoft.com/en-us/library/d1y53h2a.aspx
http://pubs.opengroup.org/onlinepubs/9699919799/functions/mktime.html
This patch adds the workaround against wrong interpretation of TZ on native
Windows to the 'mktime' function.

Also it fixes a three macrology problems:
- The use of module 'timegm' or 'mktime-internal' without module 'mktime'
could lead to a link error w.r.t. to symbol 'rpl_mktime' when the user
wants to use the mktime() function. (I think. Haven't checked.)
- The use of module 'mktime-internal' without module 'mktime' would still
define a function 'mktime' in mktime.o.
- When cross-compiling, it now prints "checking for working mktime... guessing no"
instead of "checking for working mktime... no".

Ultimately, this macrology complexity is due to the fact that so much stuff
is contained in a single source file, lib/mktime.c. It would be simpler if
the mktime_internal function was a different compilation unit; then the
'mktime-internal' module could do AC_LIBOBJ([mktime-internal]) instead of
AC_LIBOBJ([mktime]).


2017-04-30 Bruno Haible <***@clisp.org>

mktime: Work around TZ problem on native Windows.
* lib/mktime.c: Add #ifs to make the algorithmic workaround independent
from the native Windows workaround.
* m4/mktime.m4 (gl_FUNC_MKTIME_WORKS): New macro, extracted from
gl_FUNC_MKTIME. If guessing, set gl_cv_func_working_mktime to
'guessing no'.
(gl_FUNC_MKTIME): Require it. Require AC_CANONICAL_HOST.
Set REPLACE_MKTIME to 1 on native Windows. Define NEED_MKTIME_WORKING,
NEED_MKTIME_WINDOWS.
(gl_FUNC_MKTIME_INTERNAL): Require gl_FUNC_MKTIME_WORKS, not
gl_FUNC_MKTIME. Set WANT_MKTIME_INTERNAL, not REPLACE_MKTIME. Define
NEED_MKTIME_INTERNAL.
* m4/timegm.m4 (gl_FUNC_TIMEGM): Require gl_FUNC_MKTIME_WORKS, not
gl_FUNC_MKTIME. Cope with 'guessing yes' value.
* modules/mktime-internal (configure.ac): Test WANT_MKTIME_INTERNAL,
not REPLACE_MKTIME.
* doc/posix-functions/mktime.texi: Mention the native Windows
workaround.

diff --git a/doc/posix-functions/mktime.texi b/doc/posix-functions/mktime.texi
index ffb7b79..35a9a41 100644
--- a/doc/posix-functions/mktime.texi
+++ b/doc/posix-functions/mktime.texi
@@ -9,6 +9,9 @@ Gnulib module: mktime
Portability problems fixed by Gnulib:
@itemize
@item
+On native Windows platforms (mingw, MSVC), this function works incorrectly
+when the environment variable @code{TZ} has been set by Cygwin.
+@item
@code{mktime} may go into an endless loop on some platforms.
@item
@code{mktime} may occasionally return wrong results on some platforms.
@@ -16,7 +19,4 @@ Portability problems fixed by Gnulib:

Portability problems not fixed by Gnulib:
@itemize
-@item
-On native Windows platforms (mingw, MSVC), this function works incorrectly
-when the environment variable @code{TZ} has been set by Cygwin.
@end itemize
diff --git a/lib/mktime.c b/lib/mktime.c
index 2efd44a..a78d960 100644
--- a/lib/mktime.c
+++ b/lib/mktime.c
@@ -23,6 +23,19 @@
# define DEBUG_MKTIME 0
#endif

+/* The following macros influence what gets defined when this file is compiled:
+
+ Macro/expression Which gnulib module This compilation unit
+ should define
+
+ NEED_MKTIME_WORKING mktime rpl_mktime
+ || NEED_MKTIME_WINDOWS
+
+ NEED_MKTIME_INTERNAL mktime-internal mktime_internal
+
+ DEBUG_MKTIME (defined manually) my_mktime, main
+ */
+
#if !defined _LIBC && !DEBUG_MKTIME
# include <config.h>
#endif
@@ -51,6 +64,13 @@
# define mktime my_mktime
#endif

+#if NEED_MKTIME_WINDOWS /* on native Windows */
+# include <stdlib.h>
+# include <string.h>
+#endif
+
+#if NEED_MKTIME_WORKING || NEED_MKTIME_INTERNAL || DEBUG_MKTIME
+
/* A signed type that can represent an integer number of years
multiplied by three times the number of seconds in a year. It is
needed when converting a tm_year value times the number of seconds
@@ -458,25 +478,46 @@ __mktime_internal (struct tm *tp,
return t;
}

+#endif /* NEED_MKTIME_WORKING || NEED_MKTIME_INTERNAL || DEBUG_MKTIME */
+
+#if NEED_MKTIME_WORKING || NEED_MKTIME_WINDOWS || DEBUG_MKTIME

+# if NEED_MKTIME_WORKING || DEBUG_MKTIME
static mktime_offset_t localtime_offset;
+# endif

/* Convert *TP to a time_t value. */
time_t
mktime (struct tm *tp)
{
-#ifdef _LIBC
+# if NEED_MKTIME_WINDOWS
+ /* If the environment variable TZ has been set by Cygwin, neutralize it.
+ The Microsoft CRT interprets TZ differently than Cygwin and produces
+ incorrect results if TZ has the syntax used by Cygwin. */
+ const char *tz = getenv ("TZ");
+ if (tz != NULL && strchr (tz, '/') != NULL)
+ _putenv ("TZ=");
+# endif
+
+# if NEED_MKTIME_WORKING || DEBUG_MKTIME
+# ifdef _LIBC
/* POSIX.1 8.1.1 requires that whenever mktime() is called, the
time zone names contained in the external variable 'tzname' shall
be set as if the tzset() function had been called. */
__tzset ();
-#elif HAVE_TZSET
+# elif HAVE_TZSET
tzset ();
-#endif
+# endif

return __mktime_internal (tp, __localtime_r, &localtime_offset);
+# else
+# undef mktime
+ return mktime (tp);
+# endif
}

+#endif /* NEED_MKTIME_WORKING || NEED_MKTIME_WINDOWS || DEBUG_MKTIME */
+
#ifdef weak_alias
weak_alias (mktime, timelocal)
#endif
diff --git a/m4/mktime.m4 b/m4/mktime.m4
index d594ddc..31da65e 100644
--- a/m4/mktime.m4
+++ b/m4/mktime.m4
@@ -1,4 +1,4 @@
-# serial 27
+# serial 28
dnl Copyright (C) 2002-2003, 2005-2007, 2009-2017 Free Software Foundation,
dnl Inc.
dnl This file is free software; the Free Software Foundation
@@ -21,9 +21,9 @@ AC_DEFUN([gl_TIME_T_IS_SIGNED],
fi
])

-AC_DEFUN([gl_FUNC_MKTIME],
+dnl Test whether mktime works. Set gl_cv_func_working_mktime.
+AC_DEFUN([gl_FUNC_MKTIME_WORKS],
[
- AC_REQUIRE([gl_HEADER_TIME_H_DEFAULTS])
AC_REQUIRE([gl_TIME_T_IS_SIGNED])

dnl We don't use AC_FUNC_MKTIME any more, because it is no longer maintained
@@ -239,29 +239,50 @@ main ()
}]])],
[gl_cv_func_working_mktime=yes],
[gl_cv_func_working_mktime=no],
- [gl_cv_func_working_mktime=no])
+ [gl_cv_func_working_mktime="guessing no"])
])
+])
+
+dnl Main macro of module 'mktime'.
+AC_DEFUN([gl_FUNC_MKTIME],
+[
+ AC_REQUIRE([gl_HEADER_TIME_H_DEFAULTS])
+ AC_REQUIRE([AC_CANONICAL_HOST])
+ AC_REQUIRE([gl_FUNC_MKTIME_WORKS])

- if test $gl_cv_func_working_mktime = no; then
+ REPLACE_MKTIME=0
+ if test "$gl_cv_func_working_mktime" != yes; then
REPLACE_MKTIME=1
- else
- REPLACE_MKTIME=0
+ AC_DEFINE([NEED_MKTIME_WORKING], [1],
+ [Define if the compilation of mktime.c should define 'mktime'
+ with the algorithmic workarounds.])
fi
+ case "$host_os" in
+ mingw*)
+ REPLACE_MKTIME=1
+ AC_DEFINE([NEED_MKTIME_WINDOWS], [1],
+ [Define if the compilation of mktime.c should define 'mktime'
+ with the native Windows TZ workaround.])
+ ;;
+ esac
])

+dnl Main macro of module 'mktime-internal'.
AC_DEFUN([gl_FUNC_MKTIME_INTERNAL], [
- AC_REQUIRE([gl_FUNC_MKTIME])
- if test $REPLACE_MKTIME = 0; then
- dnl BeOS has __mktime_internal in libc, but other platforms don't.
- AC_CHECK_FUNC([__mktime_internal],
- [AC_DEFINE([mktime_internal], [__mktime_internal],
- [Define to the real name of the mktime_internal function.])
- ],
- [dnl mktime works but it doesn't export __mktime_internal,
- dnl so we need to substitute our own mktime implementation.
- REPLACE_MKTIME=1
- ])
- fi
+ AC_REQUIRE([gl_FUNC_MKTIME_WORKS])
+
+ WANT_MKTIME_INTERNAL=0
+ dnl BeOS has __mktime_internal in libc, but other platforms don't.
+ AC_CHECK_FUNC([__mktime_internal],
+ [AC_DEFINE([mktime_internal], [__mktime_internal],
+ [Define to the real name of the mktime_internal function.])
+ ],
+ [dnl mktime works but it doesn't export __mktime_internal,
+ dnl so we need to substitute our own mktime implementation.
+ WANT_MKTIME_INTERNAL=1
+ AC_DEFINE([NEED_MKTIME_INTERNAL], [1],
+ [Define if the compilation of mktime.c should define 'mktime_internal'.])
+ ])
])

# Prerequisites of lib/mktime.c.
diff --git a/m4/timegm.m4 b/m4/timegm.m4
index 510e25a..1f18552 100644
--- a/m4/timegm.m4
+++ b/m4/timegm.m4
@@ -1,4 +1,4 @@
-# timegm.m4 serial 11
+# timegm.m4 serial 12
dnl Copyright (C) 2003, 2007, 2009-2017 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
@@ -7,11 +7,11 @@ dnl with or without modifications, as long as this notice is preserved.
AC_DEFUN([gl_FUNC_TIMEGM],
[
AC_REQUIRE([gl_HEADER_TIME_H_DEFAULTS])
- AC_REQUIRE([gl_FUNC_MKTIME])
+ AC_REQUIRE([gl_FUNC_MKTIME_WORKS])
REPLACE_TIMEGM=0
AC_CHECK_FUNCS_ONCE([timegm])
if test $ac_cv_func_timegm = yes; then
- if test $gl_cv_func_working_mktime = no; then
+ if test "$gl_cv_func_working_mktime" != yes; then
# Assume that timegm is buggy if mktime is.
REPLACE_TIMEGM=1
fi
diff --git a/modules/mktime-internal b/modules/mktime-internal
index f9cf460..1465c90 100644
--- a/modules/mktime-internal
+++ b/modules/mktime-internal
@@ -10,7 +10,7 @@ mktime

configure.ac:
gl_FUNC_MKTIME_INTERNAL
-if test $REPLACE_MKTIME = 1; then
+if test $WANT_MKTIME_INTERNAL = 1; then
AC_LIBOBJ([mktime])
gl_PREREQ_MKTIME
fi
Bruno Haible
2017-04-30 17:52:53 UTC
Permalink
Post by Bruno Haible
* Some which should obey TZ, just that they should ignore the values set by
strftime, _strftime_l
https://msdn.microsoft.com/en-us/library/fe06s4ak.aspx
http://pubs.opengroup.org/onlinepubs/9699919799/functions/strftime.html
This implements the workaround. Unfortunately, the module name 'strftime' was
already taken.


2017-04-30 Bruno Haible <***@clisp.org>

strftime-fixes: New module.
* lib/time.in.h (strftime): New declaration.
* lib/strftime-fixes.c: New file.
* m4/strftime.m4 (gl_FUNC_GNU_STRFTIME): Inline gl_FUNC_STRFTIME macro.
(gl_FUNC_STRFTIME): Remove macro.
* m4/strftime-fixes.m4: New file.
* m4/time_h.m4 (gl_HEADER_TIME_H_DEFAULTS): Initialize GNULIB_STRFTIME,
REPLACE_STRFTIME.
* modules/time (Makefile.am): Substitute GNULIB_STRFTIME,
REPLACE_STRFTIME.
* modules/strftime-fixes: New file.
* doc/posix-functions/strftime.texi: Mention the new module.

diff --git a/doc/posix-functions/strftime.texi b/doc/posix-functions/strftime.texi
index d371818..0c2d992 100644
--- a/doc/posix-functions/strftime.texi
+++ b/doc/posix-functions/strftime.texi
@@ -4,18 +4,18 @@

POSIX specification:@* @url{http://www.opengroup.org/onlinepubs/9699919799/functions/strftime.html}

-Gnulib module: ---
+Gnulib module: strftime-fixes

Portability problems fixed by Gnulib:
@itemize
+@item
+On native Windows platforms (mingw, MSVC), this function works incorrectly
+when the environment variable @code{TZ} has been set by Cygwin.
@end itemize

Portability problems not fixed by Gnulib:
@itemize
@item
-On native Windows platforms (mingw, MSVC), this function works incorrectly
-when the environment variable @code{TZ} has been set by Cygwin.
-@item
The Windows C runtime library (which is used by MinGW) does not
support the %e specifier (and possibly the other more recent SUS
specifiers too, i.e., %C, %D, %h, %n, %r, %R, %t, and %T).
diff --git a/lib/strftime-fixes.c b/lib/strftime-fixes.c
new file mode 100644
index 0000000..6618c13
--- /dev/null
+++ b/lib/strftime-fixes.c
@@ -0,0 +1,40 @@
+/* Work around platform bugs in strftime.
+ Copyright (C) 2017 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+/* Specification. */
+#include <time.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#undef strftime
+
+size_t
+rpl_strftime (char *buf, size_t bufsize, const char *format, const struct tm *tp)
+{
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+ /* If the environment variable TZ has been set by Cygwin, neutralize it.
+ The Microsoft CRT interprets TZ differently than Cygwin and produces
+ incorrect results if TZ has the syntax used by Cygwin. */
+ const char *tz = getenv ("TZ");
+ if (tz != NULL && strchr (tz, '/') != NULL)
+ _putenv ("TZ=");
+#endif
+
+ return strftime (buf, bufsize, format, tp);
+}
diff --git a/lib/time.in.h b/lib/time.in.h
index 2587cdd..8f748ec 100644
--- a/lib/time.in.h
+++ b/lib/time.in.h
@@ -249,6 +249,25 @@ _GL_CXXALIAS_SYS (ctime, char *, (time_t const *__tp));
_GL_CXXALIASWARN (ctime);
# endif

+/* Convert *TP to a date and time string. See
+ <http://pubs.opengroup.org/onlinepubs/9699919799/functions/strftime.html>. */
+# if @GNULIB_STRFTIME@
+# if @REPLACE_STRFTIME@
+# if !(defined __cplusplus && defined GNULIB_NAMESPACE)
+# define strftime rpl_strftime
+# endif
+_GL_FUNCDECL_RPL (strftime, size_t, (char *__buf, size_t __bufsize,
+ const char *__fmt, const struct tm *__tp)
+ _GL_ARG_NONNULL ((1, 3, 4)));
+_GL_CXXALIAS_RPL (strftime, size_t, (char *__buf, size_t __bufsize,
+ const char *__fmt, const struct tm *__tp));
+# else
+_GL_CXXALIAS_SYS (strftime, size_t, (char *__buf, size_t __bufsize,
+ const char *__fmt, const struct tm *__tp));
+# endif
+_GL_CXXALIASWARN (strftime);
+# endif
+
# if defined _GNU_SOURCE && @GNULIB_TIME_RZ@ && ! @HAVE_TIMEZONE_T@
typedef struct tm_zone *timezone_t;
_GL_FUNCDECL_SYS (tzalloc, timezone_t, (char const *__name));
diff --git a/m4/strftime-fixes.m4 b/m4/strftime-fixes.m4
new file mode 100644
index 0000000..dbeb4d9
--- /dev/null
+++ b/m4/strftime-fixes.m4
@@ -0,0 +1,15 @@
+# strftime-fixes.m4 serial 1
+dnl Copyright (C) 2017 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_STRFTIME],
+[
+ AC_REQUIRE([gl_HEADER_TIME_H_DEFAULTS])
+ AC_REQUIRE([AC_CANONICAL_HOST])
+ case "$host_os" in
+ mingw*) REPLACE_STRFTIME=1 ;;
+ *) REPLACE_STRFTIME=0 ;;
+ esac
+])
diff --git a/m4/strftime.m4 b/m4/strftime.m4
index 3a5db9b..d2dac9e 100644
--- a/m4/strftime.m4
+++ b/m4/strftime.m4
@@ -1,4 +1,4 @@
-# serial 33
+# serial 34

# Copyright (C) 1996-1997, 1999-2007, 2009-2017 Free Software Foundation, Inc.
#
@@ -10,12 +10,6 @@

AC_DEFUN([gl_FUNC_GNU_STRFTIME],
[
- gl_FUNC_STRFTIME
-])
-
-# These are the prerequisite macros for GNU's strftime.c replacement.
-AC_DEFUN([gl_FUNC_STRFTIME],
-[
# This defines (or not) HAVE_TZNAME and HAVE_TM_ZONE.
AC_REQUIRE([AC_STRUCT_TIMEZONE])

diff --git a/m4/time_h.m4 b/m4/time_h.m4
index 2eaf3ae..e0f663e 100644
--- a/m4/time_h.m4
+++ b/m4/time_h.m4
@@ -108,6 +108,7 @@ AC_DEFUN([gl_HEADER_TIME_H_DEFAULTS],
GNULIB_MKTIME=0; AC_SUBST([GNULIB_MKTIME])
GNULIB_LOCALTIME=0; AC_SUBST([GNULIB_LOCALTIME])
GNULIB_NANOSLEEP=0; AC_SUBST([GNULIB_NANOSLEEP])
+ GNULIB_STRFTIME=0; AC_SUBST([GNULIB_STRFTIME])
GNULIB_STRPTIME=0; AC_SUBST([GNULIB_STRPTIME])
GNULIB_TIMEGM=0; AC_SUBST([GNULIB_TIMEGM])
GNULIB_TIME_R=0; AC_SUBST([GNULIB_TIME_R])
@@ -124,6 +125,7 @@ AC_DEFUN([gl_HEADER_TIME_H_DEFAULTS],
REPLACE_LOCALTIME_R=GNULIB_PORTCHECK; AC_SUBST([REPLACE_LOCALTIME_R])
REPLACE_MKTIME=GNULIB_PORTCHECK; AC_SUBST([REPLACE_MKTIME])
REPLACE_NANOSLEEP=GNULIB_PORTCHECK; AC_SUBST([REPLACE_NANOSLEEP])
+ REPLACE_STRFTIME=GNULIB_PORTCHECK; AC_SUBST([REPLACE_STRFTIME])
REPLACE_TIMEGM=GNULIB_PORTCHECK; AC_SUBST([REPLACE_TIMEGM])

dnl Hack so that the time module doesn't depend on the sys_time module.
diff --git a/modules/strftime-fixes b/modules/strftime-fixes
new file mode 100644
index 0000000..4a7bebf
--- /dev/null
+++ b/modules/strftime-fixes
@@ -0,0 +1,27 @@
+Description:
+strftime() function: convert broken-down time to string.
+
+Files:
+lib/strftime-fixes.c
+m4/strftime-fixes.m4
+
+Depends-on:
+time
+
+configure.ac:
+gl_FUNC_STRFTIME
+if test $REPLACE_STRFTIME = 1; then
+ AC_LIBOBJ([strftime-fixes])
+fi
+gl_TIME_MODULE_INDICATOR([strftime])
+
+Makefile.am:
+
+Include:
+<time.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all
diff --git a/modules/time b/modules/time
index 4bbfbd4..5cb8ac2 100644
--- a/modules/time
+++ b/modules/time
@@ -35,6 +35,7 @@ time.h: time.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H) $(
-e 's/@''GNULIB_LOCALTIME''@/$(GNULIB_LOCALTIME)/g' \
-e 's/@''GNULIB_MKTIME''@/$(GNULIB_MKTIME)/g' \
-e 's/@''GNULIB_NANOSLEEP''@/$(GNULIB_NANOSLEEP)/g' \
+ -e 's/@''GNULIB_STRFTIME''@/$(GNULIB_STRFTIME)/g' \
-e 's/@''GNULIB_STRPTIME''@/$(GNULIB_STRPTIME)/g' \
-e 's/@''GNULIB_TIMEGM''@/$(GNULIB_TIMEGM)/g' \
-e 's/@''GNULIB_TIME_R''@/$(GNULIB_TIME_R)/g' \
@@ -50,6 +51,7 @@ time.h: time.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H) $(
-e 's|@''REPLACE_LOCALTIME_R''@|$(REPLACE_LOCALTIME_R)|g' \
-e 's|@''REPLACE_MKTIME''@|$(REPLACE_MKTIME)|g' \
-e 's|@''REPLACE_NANOSLEEP''@|$(REPLACE_NANOSLEEP)|g' \
+ -e 's|@''REPLACE_STRFTIME''@|$(REPLACE_STRFTIME)|g' \
-e 's|@''REPLACE_TIMEGM''@|$(REPLACE_TIMEGM)|g' \
-e 's|@''PTHREAD_H_DEFINES_STRUCT_TIMESPEC''@|$(PTHREAD_H_DEFINES_STRUCT_TIMESPEC)|g' \
-e 's|@''SYS_TIME_H_DEFINES_STRUCT_TIMESPEC''@|$(SYS_TIME_H_DEFINES_STRUCT_TIMESPEC)|g' \
Bruno Haible
2017-04-30 17:54:03 UTC
Permalink
Post by Bruno Haible
* Some which should obey TZ, just that they should ignore the values set by
wcsftime, _wcsftime_l
https://msdn.microsoft.com/en-us/library/fe06s4ak.aspx
http://pubs.opengroup.org/onlinepubs/9699919799/functions/wcsftime.html
Fixed like this:


2017-04-30 Bruno Haible <***@clisp.org>

wcsftime: New module.
* lib/wchar.in.h (wcsftime): New declaration.
* lib/wcsftime.c: New file.
* m4/wcsftime.m4: New file.
* m4/wchar_h.m4 (gl_WCHAR_H): Test for wcsftime declaration.
(gl_HEADER_TIME_H_DEFAULTS): Initialize GNULIB_WCSFTIME,
HAVE_WCSFTIME, REPLACE_WCSFTIME.
* modules/wchar (Makefile.am): Substitute GNULIB_WCSFTIME,
HAVE_WCSFTIME, REPLACE_WCSFTIME.
* modules/wcsftime: New file.
* doc/posix-functions/wcsftime.texi: Mention the new module.

diff --git a/doc/posix-functions/wcsftime.texi b/doc/posix-functions/wcsftime.texi
index 8ab82c2..931b81c 100644
--- a/doc/posix-functions/wcsftime.texi
+++ b/doc/posix-functions/wcsftime.texi
@@ -4,10 +4,13 @@

POSIX specification:@* @url{http://www.opengroup.org/onlinepubs/9699919799/functions/wcsftime.html}

-Gnulib module: ---
+Gnulib module: wcsftime

Portability problems fixed by Gnulib:
@itemize
+@item
+On native Windows platforms (mingw, MSVC), this function works incorrectly
+when the environment variable @code{TZ} has been set by Cygwin.
@end itemize

Portability problems not fixed by Gnulib:
@@ -16,9 +19,6 @@ Portability problems not fixed by Gnulib:
This function is missing on some platforms:
OpenBSD 3.8, Minix 3.1.8, IRIX 5.3, Solaris 2.5.1, Cygwin 1.5.x, BeOS.
@item
-On native Windows platforms (mingw, MSVC), this function works incorrectly
-when the environment variable @code{TZ} has been set by Cygwin.
-@item
On AIX and Windows platforms, @code{wchar_t} is a 16-bit type and therefore cannot
accommodate all Unicode characters.
@end itemize
diff --git a/lib/wchar.in.h b/lib/wchar.in.h
index 4969a0c..ef155d2 100644
--- a/lib/wchar.in.h
+++ b/lib/wchar.in.h
@@ -1036,6 +1036,38 @@ _GL_WARN_ON_USE (wcswidth, "wcswidth is unportable - "
#endif


+/* Convert *TP to a date and time wide string. See
+ <http://pubs.opengroup.org/onlinepubs/9699919799/functions/wcsftime.html>. */
+#if @GNULIB_WCSFTIME@
+# if @REPLACE_WCSFTIME@
+# if !(defined __cplusplus && defined GNULIB_NAMESPACE)
+# undef wcsftime
+# define wcsftime rpl_wcsftime
+# endif
+_GL_FUNCDECL_RPL (wcsftime, size_t, (wchar_t *__buf, size_t __bufsize,
+ const wchar_t *__fmt, const struct tm *__tp)
+ _GL_ARG_NONNULL ((1, 3, 4)));
+_GL_CXXALIAS_RPL (wcsftime, size_t, (wchar_t *__buf, size_t __bufsize,
+ const wchar_t *__fmt, const struct tm *__tp));
+# else
+# if !@HAVE_WCSFTIME@
+_GL_FUNCDECL_SYS (wcsftime, size_t, (wchar_t *__buf, size_t __bufsize,
+ const wchar_t *__fmt, const struct tm *__tp)
+ _GL_ARG_NONNULL ((1, 3, 4)));
+# endif
+_GL_CXXALIAS_SYS (wcsftime, size_t, (wchar_t *__buf, size_t __bufsize,
+ const wchar_t *__fmt, const struct tm *__tp));
+# endif
+_GL_CXXALIASWARN (wcsftime);
+#elif defined GNULIB_POSIXCHECK
+# undef wcsftime
+# if HAVE_RAW_DECL_WCSFTIME
+_GL_WARN_ON_USE (wcsftime, "wcsftime is unportable - "
+ "use gnulib module wcsftime for portability");
+# endif
+#endif
+
+
#endif /* ***@GUARD_PREFIX@_WCHAR_H */
#endif /* ***@GUARD_PREFIX@_WCHAR_H */
#endif
diff --git a/lib/wcsftime.c b/lib/wcsftime.c
new file mode 100644
index 0000000..cffe2e0
--- /dev/null
+++ b/lib/wcsftime.c
@@ -0,0 +1,41 @@
+/* Work around platform bugs in wcsftime.
+ Copyright (C) 2017 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+/* Specification. */
+#include <wchar.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#undef wcsftime
+
+size_t
+rpl_wcsftime (wchar_t *buf, size_t bufsize, const wchar_t *format, const struct tm *tp)
+{
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+ /* If the environment variable TZ has been set by Cygwin, neutralize it.
+ The Microsoft CRT interprets TZ differently than Cygwin and produces
+ incorrect results if TZ has the syntax used by Cygwin. */
+ const char *tz = getenv ("TZ");
+ if (tz != NULL && strchr (tz, '/') != NULL)
+ _putenv ("TZ=");
+#endif
+
+ return wcsftime (buf, bufsize, format, tp);
+}
diff --git a/m4/wchar_h.m4 b/m4/wchar_h.m4
index d0e11a0..621cfb9 100644
--- a/m4/wchar_h.m4
+++ b/m4/wchar_h.m4
@@ -7,7 +7,7 @@ dnl with or without modifications, as long as this notice is preserved.

dnl Written by Eric Blake.

-# wchar_h.m4 serial 40
+# wchar_h.m4 serial 41

AC_DEFUN([gl_WCHAR_H],
[
@@ -53,7 +53,7 @@ AC_DEFUN([gl_WCHAR_H],
wcsrtombs wcsnrtombs wcwidth wmemchr wmemcmp wmemcpy wmemmove wmemset
wcslen wcsnlen wcscpy wcpcpy wcsncpy wcpncpy wcscat wcsncat wcscmp
wcsncmp wcscasecmp wcsncasecmp wcscoll wcsxfrm wcsdup wcschr wcsrchr
- wcscspn wcsspn wcspbrk wcsstr wcstok wcswidth
+ wcscspn wcsspn wcspbrk wcsstr wcstok wcswidth wcsftime
])
])

@@ -177,6 +177,7 @@ AC_DEFUN([gl_WCHAR_H_DEFAULTS],
GNULIB_WCSSTR=0; AC_SUBST([GNULIB_WCSSTR])
GNULIB_WCSTOK=0; AC_SUBST([GNULIB_WCSTOK])
GNULIB_WCSWIDTH=0; AC_SUBST([GNULIB_WCSWIDTH])
+ GNULIB_WCSFTIME=0; AC_SUBST([GNULIB_WCSFTIME])
dnl Assume proper GNU behavior unless another module says otherwise.
HAVE_BTOWC=1; AC_SUBST([HAVE_BTOWC])
HAVE_MBSINIT=1; AC_SUBST([HAVE_MBSINIT])
@@ -215,6 +216,7 @@ AC_DEFUN([gl_WCHAR_H_DEFAULTS],
HAVE_WCSSTR=1; AC_SUBST([HAVE_WCSSTR])
HAVE_WCSTOK=1; AC_SUBST([HAVE_WCSTOK])
HAVE_WCSWIDTH=1; AC_SUBST([HAVE_WCSWIDTH])
+ HAVE_WCSFTIME=1; AC_SUBST([HAVE_WCSFTIME])
HAVE_DECL_WCTOB=1; AC_SUBST([HAVE_DECL_WCTOB])
HAVE_DECL_WCWIDTH=1; AC_SUBST([HAVE_DECL_WCWIDTH])
REPLACE_MBSTATE_T=0; AC_SUBST([REPLACE_MBSTATE_T])
@@ -230,4 +232,5 @@ AC_DEFUN([gl_WCHAR_H_DEFAULTS],
REPLACE_WCSNRTOMBS=0; AC_SUBST([REPLACE_WCSNRTOMBS])
REPLACE_WCWIDTH=0; AC_SUBST([REPLACE_WCWIDTH])
REPLACE_WCSWIDTH=0; AC_SUBST([REPLACE_WCSWIDTH])
+ REPLACE_WCSFTIME=0; AC_SUBST([REPLACE_WCSFTIME])
])
diff --git a/m4/wcsftime.m4 b/m4/wcsftime.m4
new file mode 100644
index 0000000..a28d4d5
--- /dev/null
+++ b/m4/wcsftime.m4
@@ -0,0 +1,19 @@
+# wcsftime.m4 serial 1
+dnl Copyright (C) 2017 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_WCSFTIME],
+[
+ AC_REQUIRE([gl_WCHAR_H_DEFAULTS])
+ AC_REQUIRE([AC_CANONICAL_HOST])
+ AC_CHECK_FUNCS_ONCE([wcsftime])
+ if test $ac_cv_func_wcsftime = no; then
+ HAVE_WCSFTIME=0
+ else
+ case "$host_os" in
+ mingw*) REPLACE_WCSFTIME=1 ;;
+ esac
+ fi
+])
diff --git a/modules/wchar b/modules/wchar
index f29b200..73012c6 100644
--- a/modules/wchar
+++ b/modules/wchar
@@ -71,6 +71,7 @@ wchar.h: wchar.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H)
-e 's/@''GNULIB_WCSSTR''@/$(GNULIB_WCSSTR)/g' \
-e 's/@''GNULIB_WCSTOK''@/$(GNULIB_WCSTOK)/g' \
-e 's/@''GNULIB_WCSWIDTH''@/$(GNULIB_WCSWIDTH)/g' \
+ -e 's/@''GNULIB_WCSFTIME''@/$(GNULIB_WCSFTIME)/g' \
< $(srcdir)/wchar.in.h | \
sed -e 's|@''HAVE_WINT_T''@|$(HAVE_WINT_T)|g' \
-e 's|@''HAVE_BTOWC''@|$(HAVE_BTOWC)|g' \
@@ -110,6 +111,7 @@ wchar.h: wchar.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H)
-e 's|@''HAVE_WCSSTR''@|$(HAVE_WCSSTR)|g' \
-e 's|@''HAVE_WCSTOK''@|$(HAVE_WCSTOK)|g' \
-e 's|@''HAVE_WCSWIDTH''@|$(HAVE_WCSWIDTH)|g' \
+ -e 's|@''HAVE_WCSFTIME''@|$(HAVE_WCSFTIME)|g' \
-e 's|@''HAVE_DECL_WCTOB''@|$(HAVE_DECL_WCTOB)|g' \
-e 's|@''HAVE_DECL_WCWIDTH''@|$(HAVE_DECL_WCWIDTH)|g' \
| \
@@ -126,6 +128,7 @@ wchar.h: wchar.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H)
-e 's|@''REPLACE_WCSNRTOMBS''@|$(REPLACE_WCSNRTOMBS)|g' \
-e 's|@''REPLACE_WCWIDTH''@|$(REPLACE_WCWIDTH)|g' \
-e 's|@''REPLACE_WCSWIDTH''@|$(REPLACE_WCSWIDTH)|g' \
+ -e 's|@''REPLACE_WCSFTIME''@|$(REPLACE_WCSFTIME)|g' \
-e '/definitions of _GL_FUNCDECL_RPL/r $(CXXDEFS_H)' \
-e '/definition of _GL_ARG_NONNULL/r $(ARG_NONNULL_H)' \
-e '/definition of _GL_WARN_ON_USE/r $(WARN_ON_USE_H)'; \
diff --git a/modules/wcsftime b/modules/wcsftime
new file mode 100644
index 0000000..d911a28
--- /dev/null
+++ b/modules/wcsftime
@@ -0,0 +1,27 @@
+Description:
+wcsftime() function: convert broken-down time to wide string.
+
+Files:
+lib/wcsftime.c
+m4/wcsftime.m4
+
+Depends-on:
+wchar
+
+configure.ac:
+gl_FUNC_WCSFTIME
+if test $REPLACE_WCSFTIME = 1; then
+ AC_LIBOBJ([wcsftime])
+fi
+gl_WCHAR_MODULE_INDICATOR([wcsftime])
+
+Makefile.am:
+
+Include:
+<wchar.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all
Bruno Haible
2017-05-01 16:40:53 UTC
Permalink
Post by Bruno Haible
* Some which should obey TZ, just that they should ignore the values set by
_tzset
https://msdn.microsoft.com/en-us/library/aa273389.aspx
https://msdn.microsoft.com/en-us/library/90s5c885.aspx
http://pubs.opengroup.org/onlinepubs/9699919799/functions/tzset.html
Here's the workaround for 'tzset'.


2017-05-01 Bruno Haible <***@clisp.org>

tzset: Work around TZ problem on native Windows.
* m4/tzset.m4 (gl_FUNC_TZSET): Require AC_CANONICAL_HOST. On native
Windows, set REPLACE_TZSET to 1.
* lib/tzset.c (tzset): On native Windows, fix TZ if necessary, and
invoke '_tzset' instead of 'tzset'.
* doc/posix-functions/tzset.texi: Mention the native Windows workaround.

* modules/time_rz (Depends-on): Add tzset.
* lib/time_rz.c (tzset): Remove fallback definition.
* m4/time_rz.m4 (gl_TIME_RZ): Don't test for tzset.

diff --git a/doc/posix-functions/tzset.texi b/doc/posix-functions/tzset.texi
index 30b147c..a457409 100644
--- a/doc/posix-functions/tzset.texi
+++ b/doc/posix-functions/tzset.texi
@@ -9,6 +9,9 @@ Gnulib module: tzset
Portability problems fixed by Gnulib:
@itemize
@item
+On native Windows platforms (mingw, MSVC), this function works incorrectly
+when the environment variable @code{TZ} has been set by Cygwin.
+@item
This function clobbers the buffer used by the localtime function on some
platforms:
Solaris 2.6.
@@ -16,7 +19,4 @@ Solaris 2.6.

Portability problems not fixed by Gnulib:
@itemize
-@item
-On native Windows platforms (mingw, MSVC), this function works incorrectly
-when the environment variable @code{TZ} has been set by Cygwin.
@end itemize
diff --git a/lib/tzset.c b/lib/tzset.c
index 1cb9822..ce854b9 100644
--- a/lib/tzset.c
+++ b/lib/tzset.c
@@ -40,7 +40,23 @@ tzset (void)
struct tm save = *localtime_buffer_addr;
#endif

+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+ /* If the environment variable TZ has been set by Cygwin, neutralize it.
+ The Microsoft CRT interprets TZ differently than Cygwin and produces
+ incorrect results if TZ has the syntax used by Cygwin. */
+ const char *tz = getenv ("TZ");
+ if (tz != NULL && strchr (tz, '/') != NULL)
+ _putenv ("TZ=");
+
+ /* On native Windows, tzset() is deprecated. Use _tzset() instead. See
+ https://msdn.microsoft.com/en-us/library/ms235451.aspx
+ https://msdn.microsoft.com/en-us/library/90s5c885.aspx */
+ _tzset ();
+#elif HAVE_TZSET
tzset ();
+#else
+ /* Do nothing. Avoid infinite recursion. */
+#endif

#if TZSET_CLOBBERS_LOCALTIME
*localtime_buffer_addr = save;
diff --git a/m4/tzset.m4 b/m4/tzset.m4
index 20939e9..08362fe 100644
--- a/m4/tzset.m4
+++ b/m4/tzset.m4
@@ -17,11 +17,13 @@ AC_DEFUN([gl_FUNC_TZSET],
[
AC_REQUIRE([gl_HEADER_TIME_H_DEFAULTS])
AC_REQUIRE([gl_LOCALTIME_BUFFER_DEFAULTS])
+ AC_REQUIRE([AC_CANONICAL_HOST])
AC_CHECK_FUNCS_ONCE([tzset])
if test $ac_cv_func_tzset = no; then
HAVE_TZSET=0
fi
gl_FUNC_TZSET_CLOBBER
+ REPLACE_TZSET=0
case "$gl_cv_func_tzset_clobber" in
*yes)
REPLACE_TZSET=1
@@ -29,9 +31,9 @@ AC_DEFUN([gl_FUNC_TZSET],
[Define if tzset clobbers localtime's static buffer.])
gl_LOCALTIME_BUFFER_NEEDED
;;
- *)
- REPLACE_TZSET=0
- ;;
+ esac
+ case "$host_os" in
+ mingw*) REPLACE_TZSET=1 ;;
esac
])

diff --git a/modules/time_rz b/modules/time_rz
index e934d55..1bc29f4 100644
--- a/modules/time_rz
+++ b/modules/time_rz
@@ -24,6 +24,7 @@ setenv [test "$HAVE_TIMEZONE_T" = 0]
stdbool [test "$HAVE_TIMEZONE_T" = 0]
time_r [test "$HAVE_TIMEZONE_T" = 0]
timegm [test "$HAVE_TIMEZONE_T" = 0]
+tzset [test "$HAVE_TIMEZONE_T" = 0]
unsetenv [test "$HAVE_TIMEZONE_T" = 0]

configure.ac:
diff --git a/lib/time_rz.c b/lib/time_rz.c
index 82f3f3f..95c7293 100644
--- a/lib/time_rz.c
+++ b/lib/time_rz.c
@@ -40,10 +40,6 @@
# define SIZE_MAX ((size_t) -1)
#endif

-#if !HAVE_TZSET
-static void tzset (void) { }
-#endif
-
/* The approximate size to use for small allocation requests. This is
the largest "small" request for the GNU C library malloc. */
enum { DEFAULT_MXFAST = 64 * sizeof (size_t) / 4 };
diff --git a/m4/time_rz.m4 b/m4/time_rz.m4
index 79060e0..079e933 100644
--- a/m4/time_rz.m4
+++ b/m4/time_rz.m4
@@ -12,7 +12,6 @@ AC_DEFUN([gl_TIME_RZ],
AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
AC_REQUIRE([gl_HEADER_SYS_TIME_H_DEFAULTS])
AC_REQUIRE([AC_STRUCT_TIMEZONE])
- AC_CHECK_FUNCS_ONCE([tzset])

AC_CHECK_TYPES([timezone_t], [], [], [[#include <time.h>]])
if test "$ac_cv_type_timezone_t" = yes; then
Paul Eggert
2017-05-02 15:52:13 UTC
Permalink
Post by Bruno Haible
+ /* If the environment variable TZ has been set by Cygwin, neutralize it.
+ The Microsoft CRT interprets TZ differently than Cygwin and produces
+ incorrect results if TZ has the syntax used by Cygwin. */
+ const char *tz = getenv ("TZ");
+ if (tz != NULL && strchr (tz, '/') != NULL)
+ _putenv ("TZ=");
I'm puzzled why setting TZ="" is desirable here. Does this cause the
Microsoft CRT to use UTC? Is TZ="" a good thing because switching to UTC
is better than the undefined behavior one would get with TZ set to a
value not documented by Microsoft? If so, perhaps a comment to that
effect would help.

Also, the test "strchr (tz, '/') != NULL" allows many Cygwin-compatible
TZ settings that Microsoft does not document support for, e.g.,
TZ="PST8PDT,M3.2.0,M11.1.0" for Los Angeles. Admittedly most Cygwin
users in L.A. probably just use TZ="America/Los_Angeles" so fixing this
is not that important. Still, if the intent is to limit the TZ value to
what Microsoft documents, I suppose you could nuke the TZ value if it
does not match the C-locale ERE
"^[A-Za-z]{3}[-+]?[01]?[0-9](:[0-5][0-9](:[0-5][0-9])?)?([A-Za-z]{3})?$",
or maybe put in a FIXME comment to that effect.
Bruno Haible
2017-05-02 20:19:16 UTC
Permalink
Hi Paul,
Post by Paul Eggert
Post by Bruno Haible
+ /* If the environment variable TZ has been set by Cygwin, neutralize it.
+ The Microsoft CRT interprets TZ differently than Cygwin and produces
+ incorrect results if TZ has the syntax used by Cygwin. */
+ const char *tz = getenv ("TZ");
+ if (tz != NULL && strchr (tz, '/') != NULL)
+ _putenv ("TZ=");
I'm puzzled why setting TZ="" is desirable here. Does this cause the
Microsoft CRT to use UTC?
No. An empty or absent TZ environment variable, for the Microsoft CRT, means
the time zone that the user has set in the Windows Control Panel.

Only for Cygwin, an empty or absent TZ environment variable means GMT.
I find this a poor choice, because
- When the user changes the time zone through the Windows Control Panel
(or even automatically, when he's travelling), he has to either terminate
the Cygwin terminal window or change the TZ variable in there.
- Cygwin programs run in Windows; it's not adequate to have a default
the choices made in the Control Panel. (Gnulib's 'localename' module,
for instance, makes sure to use the settings from the Control Panel on
Windows or Mac OS.)
Post by Paul Eggert
Is TZ="" a good thing because switching to UTC
is better than the undefined behavior one would get with TZ set to a
value not documented by Microsoft?
No, setting TZ="" is a good thing because some developers use Cygwin
as a development environment for native Windows programs, and it is expected
that such a program produces identical results when run from a Cygwin terminal
window than from a cmd.exe window.
Post by Paul Eggert
Also, the test "strchr (tz, '/') != NULL" allows many Cygwin-compatible
TZ settings that Microsoft does not document support for, e.g.,
TZ="PST8PDT,M3.2.0,M11.1.0" for Los Angeles. Admittedly most Cygwin
users in L.A. probably just use TZ="America/Los_Angeles" so fixing this
is not that important. Still, if the intent is to limit the TZ value to
what Microsoft documents, I suppose you could nuke the TZ value if it
does not match the C-locale ERE
"^[A-Za-z]{3}[-+]?[01]?[0-9](:[0-5][0-9](:[0-5][0-9])?)?([A-Za-z]{3})?$",
or maybe put in a FIXME comment to that effect.
The full set of TZ values understood by MSVC CRT is not documented. (Maybe
there are more valid strings than those that fit your regexp?)

The full set of TZ values understood by Cygwin is not documented either. But
what matters most to me are those that Cygwin sets without the user being
aware of. (*)

So the borderline between both sets is guesswork. I guessed that
"contains a slash" vs. "does not contain a slash" is a reasonable distinction.
Plus, it's easy to implement in less than 1 line of code.

(*) This happens by /etc/profile.d/tzset.sh, which uses the 'tzset' program
to determine the value. The 'tzset' program is implemented in Cygwin's
winsup/utils/tzset.c
winsup/utils/tzmap.h
and all the resulting values contain a slash, except for
CST6CDT
EST5EDT
MST7MDT
PST8PDT
which follow the syntax understood by Microsoft's CRT [1].

[1] https://msdn.microsoft.com/en-us/library/90s5c885.aspx


How about this revised comment?

diff --git a/lib/tzset.c b/lib/tzset.c
index ce854b9..90f633a 100644
--- a/lib/tzset.c
+++ b/lib/tzset.c
@@ -41,9 +41,18 @@ tzset (void)
#endif

#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
- /* If the environment variable TZ has been set by Cygwin, neutralize it.
- The Microsoft CRT interprets TZ differently than Cygwin and produces
- incorrect results if TZ has the syntax used by Cygwin. */
+ /* Rectify the value of the environment variable TZ.
+ There are two possible kinds of such values:
+ - Traditional US time zone names, e.g. "PST8PDT",
+ - tzdata time zone names, based on geography. They contain one
+ or more slashes.
+ The Microsoft CRT understands only the first kind, see
+ <https://msdn.microsoft.com/en-us/library/90s5c885.aspx>.
+ It produces incorrect results if the value of TZ is of the second kind.
+ But in a Cygwin environment, /etc/profile.d/tzset.sh sets TZ to
+ a value of the second kind for most geographies. If this is the case,
+ neutralize it. For the Microsoft CRT, an absent of empty TZ means
+ the time zone that the user has set in the Windows Control Panel. */
const char *tz = getenv ("TZ");
if (tz != NULL && strchr (tz, '/') != NULL)
_putenv ("TZ=");
Paul Eggert
2017-05-03 05:18:12 UTC
Permalink
Post by Bruno Haible
Only for Cygwin, an empty or absent TZ environment variable means GMT.
That's weird; I don't know of any other system that does that. In many systems,
an empty but set TZ means UTC0 without leap seconds; but an unset TZ typically
means "ask the file system for the time zone", e.g., /etc/localtime or something
like that. Perhaps the Cygwin folks could be talked into changing an unset TZ to
mean "ask the Windows Control Panel".
Post by Bruno Haible
The full set of TZ values understood by MSVC CRT is not documented. (Maybe
there are more valid strings than those that fit your regexp?)
Maybe there are. However, I think the ERE captures all the values that Microsoft
documents, and this should be good enough.
Post by Bruno Haible
How about this revised comment?
+ - Traditional US time zone names, e.g. "PST8PDT",
+ - tzdata time zone names, based on geography. They contain one
+ or more slashes.
As a point of terminology, "PST8PDT" is as much of a tzdata time zone name as
"America/Los_Angeles" is. They are both implemented by consulting a file by that
name. And some of the slashless tzdata names are based on geography, e.g.,
"Singapore", "GB-Eire" (these are mostly present for backward compatibility, but
some people still use them). Perhaps it would be better to summarize things by
wording the comment something like this:


TZ values are of two kinds:

- Values supported by the Microsoft CRT, e.g., "PST+8PDT". See
<https://msdn.microsoft.com/en-us/library/90s5c885.aspx>. The documented values
of this form are matched by the POSIX extended regular expression
"^[A-Za-z]{3}[-+]?[01]?[0-9](:[0-5][0-9](:[0-5][0-9])?)?([A-Za-z]{3})?$".

- Values supported by Cygwin, e.g., "America/Los_Angeles". Typically, each of
these values corresponds to the name of a file installed somewhere on the
system. However, some of these values are analyzed programmatically based on
rules specified by POSIX, e.g., "PST8PDT,M3.2.0,M11.1.0"; see
<http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03>.

The two kinds of TZ values overlap, e.g., "PST8PDT" is valid in both
implementations. However, most TZ values supported by Cygwin do not work with
the Microsoft CRT, which silently uses UTC when given such values. In practice
most of these troublesome TZ values contain '/', and no TZ value supported by
the Microsoft CRT contains '/', so as a heuristic neutralize any TZ value
containing '/'. For the Microsoft CRT, an absent or empty TZ means the time zone
that the user has set in the Windows Control Panel.
Post by Bruno Haible
+ neutralize it. For the Microsoft CRT, an absent of empty TZ means
absent of -> absent or
Ken Brown
2017-05-03 11:31:47 UTC
Permalink
Post by Paul Eggert
Post by Bruno Haible
Only for Cygwin, an empty or absent TZ environment variable means GMT.
That's weird; I don't know of any other system that does that. In many systems,
an empty but set TZ means UTC0 without leap seconds; but an unset TZ typically
means "ask the file system for the time zone", e.g., /etc/localtime or something
like that. Perhaps the Cygwin folks could be talked into changing an unset TZ to
mean "ask the Windows Control Panel".
This seems to already be happening. Here's what I see on my Cygwin system:

$ echo $TZ
America/New_York

$ date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 07:29:03 -0400 (EDT)

$ TZ= date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 11:29:14 +0000 (GMT)

$ unset TZ

$ date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 07:29:30 -0400 (EDT)

Ken
Paul Eggert
2017-05-03 15:24:37 UTC
Permalink
Post by Ken Brown
Post by Paul Eggert
Perhaps the Cygwin folks could be talked into changing an unset TZ to
mean "ask the Windows Control Panel".
$ echo $TZ
America/New_York
$ date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 07:29:03 -0400 (EDT)
$ TZ= date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 11:29:14 +0000 (GMT)
$ unset TZ
$ date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 07:29:30 -0400 (EDT)
Hmm, but is the last output line because the Windows Control Panel
specifies New York time, or because there is a 'localtime' file
maintained by Cygwin that specifies New York time? I'm guessing the
latter, since that's what glibc does. The former is what Bruno is asking
for.
Marco Atzeri
2017-05-03 15:56:51 UTC
Permalink
Post by Paul Eggert
Post by Ken Brown
Post by Paul Eggert
Perhaps the Cygwin folks could be talked into changing an unset TZ to
mean "ask the Windows Control Panel".
$ echo $TZ
America/New_York
$ date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 07:29:03 -0400 (EDT)
$ TZ= date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 11:29:14 +0000 (GMT)
$ unset TZ
$ date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 07:29:30 -0400 (EDT)
Hmm, but is the last output line because the Windows Control Panel
specifies New York time, or because there is a 'localtime' file
maintained by Cygwin that specifies New York time? I'm guessing the
latter, since that's what glibc does. The former is what Bruno is asking
for.
It is linked to Windows Control Panel, changing it to different
settings I have in the same Cygwin session :

$ unset TZ

$ date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 17:51:52 +0200 (WEDT)

$ date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 18:53:26 +0300 (TST)

$ date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 22:53:46 +0700 (SEAST)

Regards
Marco
Ken Brown
2017-05-03 16:08:58 UTC
Permalink
Post by Paul Eggert
Post by Ken Brown
Post by Paul Eggert
Perhaps the Cygwin folks could be talked into changing an unset TZ to
mean "ask the Windows Control Panel".
$ echo $TZ
America/New_York
$ date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 07:29:03 -0400 (EDT)
$ TZ= date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 11:29:14 +0000 (GMT)
$ unset TZ
$ date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 07:29:30 -0400 (EDT)
Hmm, but is the last output line because the Windows Control Panel
specifies New York time, or because there is a 'localtime' file
maintained by Cygwin that specifies New York time? I'm guessing the
latter, since that's what glibc does. The former is what Bruno is asking
for.
There's no localtime file. If TZ is not set, then Cygwin calls
tzsetwall (defined in winsup/cygwin/localtime.cc), which gets the time
zone from the Windows function GetTimeZoneInformation. I verified this
by running the date command above under gdb.

Ken
Bruno Haible
2017-05-09 19:40:16 UTC
Permalink
Post by Ken Brown
$ echo $TZ
America/New_York
$ date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 07:29:03 -0400 (EDT)
$ TZ= date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 11:29:14 +0000 (GMT)
$ unset TZ
$ date +'%Y-%m-%d %H:%M:%S %z (%Z)'
2017-05-03 07:29:30 -0400 (EDT)
Thanks for correcting me. I had (incorrectly) assumed that an unset value
and an empty value are equivalent, like for LANG.

I've now brought up the issue on the Cygwin mailing list:
<https://cygwin.com/ml/cygwin/2017-05/msg00122.html>
Let's see...

Bruno

Bruno Haible
2017-05-09 19:35:05 UTC
Permalink
Hi Paul,
Post by Paul Eggert
Post by Bruno Haible
Only for Cygwin, an empty or absent TZ environment variable means GMT.
That's weird; I don't know of any other system that does that.
Misunderstanding: I meant only among the platforms on Windows. I.e. for mingw and
MSVC, an empty or absent TZ environment variable means the system's notion of
time zone.
Post by Paul Eggert
Post by Bruno Haible
How about this revised comment?
+ - Traditional US time zone names, e.g. "PST8PDT",
+ - tzdata time zone names, based on geography. They contain one
+ or more slashes.
As a point of terminology, "PST8PDT" is as much of a tzdata time zone name as
"America/Los_Angeles" is. They are both implemented by consulting a file by that
name. And some of the slashless tzdata names are based on geography, e.g.,
"Singapore", "GB-Eire" (these are mostly present for backward compatibility, but
some people still use them). Perhaps it would be better to summarize things by
- Values supported by the Microsoft CRT, e.g., "PST+8PDT". See
<https://msdn.microsoft.com/en-us/library/90s5c885.aspx>. The documented values
of this form are matched by the POSIX extended regular expression
"^[A-Za-z]{3}[-+]?[01]?[0-9](:[0-5][0-9](:[0-5][0-9])?)?([A-Za-z]{3})?$".
- Values supported by Cygwin, e.g., "America/Los_Angeles". Typically, each of
these values corresponds to the name of a file installed somewhere on the
system. However, some of these values are analyzed programmatically based on
rules specified by POSIX, e.g., "PST8PDT,M3.2.0,M11.1.0"; see
<http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03>.
The two kinds of TZ values overlap, e.g., "PST8PDT" is valid in both
implementations. However, most TZ values supported by Cygwin do not work with
the Microsoft CRT, which silently uses UTC when given such values. In practice
most of these troublesome TZ values contain '/', and no TZ value supported by
the Microsoft CRT contains '/', so as a heuristic neutralize any TZ value
containing '/'. For the Microsoft CRT, an absent or empty TZ means the time zone
that the user has set in the Windows Control Panel.
Thanks for the explanations. However, your wording is quite confusing to me,
because it talks about the possible syntaxes and their different interpretations
at the same time, and because of the overlap. For clarity, I prefer to talk
about disjoint cases. Here's what I have come up with:


2017-05-09 Bruno Haible <***@clisp.org>

tzset: Expand comment about TZ problem on native Windows.
* lib/tzset.c (tzset): Elaborate comment, based on explanations by
Paul Eggert.
* lib/ctime.c (rpl_ctime): Likewise.
* lib/localtime.c (rpl_localtime): Likewise.
* lib/mktime.c (mktime): Likewise.
* lib/strftime-fixes.c (rpl_strftime): Likewise.
* lib/wcsftime.c (rpl_wcsftime): Likewise.

diff --git a/lib/tzset.c b/lib/tzset.c
index ce854b9..bec4dfe 100644
--- a/lib/tzset.c
+++ b/lib/tzset.c
@@ -41,9 +41,28 @@ tzset (void)
#endif

#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
- /* If the environment variable TZ has been set by Cygwin, neutralize it.
- The Microsoft CRT interprets TZ differently than Cygwin and produces
- incorrect results if TZ has the syntax used by Cygwin. */
+ /* Rectify the value of the environment variable TZ.
+ There are four possible kinds of such values:
+ - Traditional US time zone names, e.g. "PST8PDT". Syntax: see
+ <https://msdn.microsoft.com/en-us/library/90s5c885.aspx>
+ - Time zone names based on geography, that contain one or more
+ slashes, e.g. "Europe/Moscow".
+ - Time zone names based on geography, without slashes, e.g.
+ "Singapore".
+ - Time zone names that contain explicit DST rules. Syntax: see
+ <http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03>
+ The Microsoft CRT understands only the first kind. It produces incorrect
+ results if the value of TZ is of the other kinds.
+ But in a Cygwin environment, /etc/profile.d/tzset.sh sets TZ to a value
+ of the second kind for most geographies, or of the first kind in a few
+ other geographies. If it is of the second kind, neutralize it. For the
+ Microsoft CRT, an absent or empty TZ means the time zone that the user
+ has set in the Windows Control Panel.
+ If the value of TZ is of the third or fourth kind -- Cygwin programs
+ understand these syntaxes as well --, it does not matter whether we
+ neutralize it or not, since these values occur only when a Cygwin user
+ has set TZ explicitly; this case is 1. rare and 2. under the user's
+ responsibility. */
const char *tz = getenv ("TZ");
if (tz != NULL && strchr (tz, '/') != NULL)
_putenv ("TZ=");
Eli Zaretskii
2017-05-04 15:01:53 UTC
Permalink
Date: Tue, 02 May 2017 22:19:16 +0200
Post by Paul Eggert
Also, the test "strchr (tz, '/') != NULL" allows many Cygwin-compatible
TZ settings that Microsoft does not document support for, e.g.,
TZ="PST8PDT,M3.2.0,M11.1.0" for Los Angeles. Admittedly most Cygwin
users in L.A. probably just use TZ="America/Los_Angeles" so fixing this
is not that important. Still, if the intent is to limit the TZ value to
what Microsoft documents, I suppose you could nuke the TZ value if it
does not match the C-locale ERE
"^[A-Za-z]{3}[-+]?[01]?[0-9](:[0-5][0-9](:[0-5][0-9])?)?([A-Za-z]{3})?$",
or maybe put in a FIXME comment to that effect.
The full set of TZ values understood by MSVC CRT is not documented. (Maybe
there are more valid strings than those that fit your regexp?)
Actually, I think the full set of values supported by the Windows CRT
is documented (Paul and you pointed out its URL on the MSDN). My only
comment to the regexp above is that the first 3 characters could be
_any_ 3 bytes: the CRT doesn't care, it just skips them, and uses the
XX:YY:ZZ construct after those 3 characters to compute the timezone
offset. Which is why the MSDN documentation explicitly says "You must
specify the correct offset from local time to UTC", because this is
the only thing that counts.
Michael Haubenwallner
2017-05-03 14:26:29 UTC
Permalink
Hi,
Post by Bruno Haible
Opinions?
$subject rings a different bell here, with glibc:
https://sourceware.org/bugzilla/show_bug.cgi?id=17646

Would it be worth to override mktime() even for glibc?

/haubi/
Paul Eggert
2017-05-03 15:30:04 UTC
Permalink
Post by Michael Haubenwallner
Would it be worth to override mktime() even for glibc?
I doubt it for that particular issue, as the problem is due more to
mktime's spec than to its implementation. I followed up at
<https://sourceware.org/bugzilla/show_bug.cgi?id=17646>.
Loading...