Fix timezone handling near year boundaries

PR libfortran/98507

libgfortran/ChangeLog:

	* intrinsics/time_1.h: Prefer clock_gettime() over
	  gettimeofday().
	* intrinsics/date_and_time.c: Fix timezone wrapping.

gcc/testsuite/ChangeLog:

	* gfortran.dg/date_and_time_1.f90: New file.
This commit is contained in:
Francois-Xavier Coudert 2021-12-16 12:40:03 +01:00 committed by François-Xavier Coudert
parent 41cc28405c
commit 3f624a624a
3 changed files with 59 additions and 14 deletions

View file

@ -0,0 +1,35 @@
! PR libfortran/98507
! { dg-do run }
program demo_time_and_date
implicit none
character(8) :: date
character(10) :: time
character(5) :: zone
integer :: val(8)
integer :: h, m
call date_and_time(values=val)
if (val(1) < 2000 .or. val(1) > 2100) stop 1
if (val(2) < 1 .or. val(2) > 12) stop 2
if (val(3) < 1 .or. val(3) > 31) stop 3
! Maximum offset is 14 hours (UTC+14)
if (val(4) < -14*60 .or. val(4) > 14*60) stop 4
if (val(5) < 0 .or. val(5) > 23) stop 5
if (val(6) < 0 .or. val(6) > 59) stop 6
if (val(7) < 0 .or. val(7) > 60) stop 7
if (val(8) < 0 .or. val(8) > 999) stop 8
call date_and_time(zone=zone)
if (len(zone) /= 0) then
! If ZONE is present, it should present the same information as
! given in VALUES(4)
if (len(zone) /= 5) stop 9
read(zone(1:3),*) h
read(zone(4:5),*) m
if (val(4) /= 60*h+m) stop 10
endif
end

View file

@ -113,9 +113,6 @@ gmtime_r (const time_t * timep, struct tm * result)
VALUES for INTEGER(kind=4) and INTEGER(kind=8).
Based on libU77's date_time_.c.
TODO :
- Check year boundaries.
*/
#define DATE_LEN 8
#define TIME_LEN 10
@ -131,7 +128,7 @@ date_and_time (char *__date, char *__time, char *__zone,
gfc_array_i4 *__values, GFC_INTEGER_4 __date_len,
GFC_INTEGER_4 __time_len, GFC_INTEGER_4 __zone_len)
{
int i;
int i, delta_day;
char date[DATE_LEN + 1];
char timec[TIME_LEN + 1];
char zone[ZONE_LEN + 1];
@ -154,9 +151,22 @@ date_and_time (char *__date, char *__time, char *__zone,
values[0] = 1900 + local_time.tm_year;
values[1] = 1 + local_time.tm_mon;
values[2] = local_time.tm_mday;
values[3] = (local_time.tm_min - UTC_time.tm_min +
60 * (local_time.tm_hour - UTC_time.tm_hour +
24 * (local_time.tm_yday - UTC_time.tm_yday)));
/* Day difference with UTC should always be -1, 0 or +1.
Near year boundaries, we may obtain a large positive (+364,
or +365 on leap years) or negative (-364, or -365 on leap years)
number, which we have to handle.
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98507
*/
delta_day = local_time.tm_yday - UTC_time.tm_yday;
if (delta_day < -1)
delta_day = 1;
else if (delta_day > 1)
delta_day = -1;
values[3] = local_time.tm_min - UTC_time.tm_min
+ 60 * (local_time.tm_hour - UTC_time.tm_hour + 24 * delta_day);
values[4] = local_time.tm_hour;
values[5] = local_time.tm_min;
values[6] = local_time.tm_sec;

View file

@ -213,19 +213,19 @@ gf_cputime (long *user_sec, long *user_usec, long *system_sec, long *system_usec
static inline int
gf_gettime (time_t * secs, long * usecs)
{
#ifdef HAVE_GETTIMEOFDAY
#ifdef HAVE_CLOCK_GETTIME
struct timespec ts;
int err = clock_gettime (CLOCK_REALTIME, &ts);
*secs = ts.tv_sec;
*usecs = ts.tv_nsec / 1000;
return err;
#elif defined(HAVE_GETTIMEOFDAY)
struct timeval tv;
int err;
err = gettimeofday (&tv, NULL);
*secs = tv.tv_sec;
*usecs = tv.tv_usec;
return err;
#elif defined(HAVE_CLOCK_GETTIME)
struct timespec ts;
int err = clock_gettime (CLOCK_REALTIME, &ts);
*secs = ts.tv_sec;
*usecs = ts.tv_nsec / 1000;
return err;
#else
time_t t = time (NULL);
*secs = t;