Improved handling of local times.

* A magic value USE_LOCAL_TIME (defined as 99999) may be passed as the
  Timezone to Convert(), instructing it to use mktime() to work
  in the local time zone, instead of using mktime_z to work in UTC
  (and then adding the specified timezone offset).
* Some old code is removed now that there's no need to find the local
  timezone offset.
* Allow either one or both of the now and zone arguments to
  parsedate() to be NULL, treating them independently.  Previously,
  if either one was NULL, the other was ignored.
* If the zone argument is specified, then the current date is calculated
  in the specified zone, not in local time.

Also add some disabled debug code.

This should fix PR lib/47916.
This commit is contained in:
apb 2014-10-08 17:38:28 +00:00
parent 4206688740
commit 82cbb6b06d
1 changed files with 37 additions and 45 deletions

View File

@ -14,7 +14,7 @@
#include <sys/cdefs.h>
#ifdef __RCSID
__RCSID("$NetBSD: parsedate.y,v 1.19 2014/10/08 14:43:48 apb Exp $");
__RCSID("$NetBSD: parsedate.y,v 1.20 2014/10/08 17:38:28 apb Exp $");
#endif
#include <stdio.h>
@ -42,6 +42,7 @@ __RCSID("$NetBSD: parsedate.y,v 1.19 2014/10/08 14:43:48 apb Exp $");
#define HOUR(x) ((time_t)(x) * 60)
#define SECSPERDAY (24L * 60L * 60L)
#define USE_LOCAL_TIME 99999 /* special case for Convert() and yyTimezone */
/*
** An entry in the lexical lookup table.
@ -618,7 +619,8 @@ Convert(
time_t Hours, /* Hour of day [0-24] */
time_t Minutes, /* Minute of hour [0-59] */
time_t Seconds, /* Second of minute [0-60] */
time_t Timezone, /* Timezone as minutes east of UTC */
time_t Timezone, /* Timezone as minutes east of UTC,
* or USE_LOCAL_TIME special case */
MERIDIAN Meridian, /* Hours are am/pm/24 hour clock */
DSTMODE DSTmode /* DST on/off/maybe */
)
@ -638,9 +640,25 @@ Convert(
default: tm.tm_isdst = -1; break;
}
/* We rely on mktime_z(NULL, ...) working in UTC, not in local time. */
if (Timezone == USE_LOCAL_TIME) {
result = mktime(&tm);
} else {
/* We rely on mktime_z(NULL, ...) working in UTC */
result = mktime_z(NULL, &tm);
result += Timezone * 60;
}
#if PARSEDATE_DEBUG
fprintf(stderr, "%s(M=%jd D=%jd Y=%jd H=%jd M=%jd S=%jd Z=%jd"
" mer=%d DST=%d)",
__func__,
(intmax_t)Month, (intmax_t)Day, (intmax_t)Year,
(intmax_t)Hours, (intmax_t)Minutes, (intmax_t)Seconds,
(intmax_t)Timezone, (int)Meridian, (int)DSTmode);
fprintf(stderr, " -> %jd", (intmax_t)result);
fprintf(stderr, " %s", ctime(&result));
#endif
return result;
}
@ -878,31 +896,10 @@ yylex(YYSTYPE *yylval, const char **yyInput)
#define TM_YEAR_ORIGIN 1900
/* Yield A - B, measured in seconds. */
static time_t
difftm (struct tm *a, struct tm *b)
{
int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
int days = (
/* difference in day of year */
a->tm_yday - b->tm_yday
/* + intervening leap days */
+ ((ay >> 2) - (by >> 2))
- (ay/100 - by/100)
+ ((ay/100 >> 2) - (by/100 >> 2))
/* + difference in years * 365 */
+ (long)(ay-by) * 365
);
return ((time_t)60*(60*(24*days + (a->tm_hour - b->tm_hour))
+ (a->tm_min - b->tm_min))
+ (a->tm_sec - b->tm_sec));
}
time_t
parsedate(const char *p, const time_t *now, const int *zone)
{
struct tm gmt, local, *gmt_ptr, *tm;
struct tm local, *tm;
time_t nowt;
int zonet;
time_t Start;
@ -913,29 +910,24 @@ parsedate(const char *p, const time_t *now, const int *zone)
saved_errno = errno;
errno = 0;
if (now == NULL || zone == NULL) {
if (now == NULL) {
now = &nowt;
zone = &zonet;
(void)time(&nowt);
gmt_ptr = gmtime_r(now, &gmt);
}
if (zone == NULL) {
zone = &zonet;
zonet = USE_LOCAL_TIME;
if ((tm = localtime_r(now, &local)) == NULL)
return -1;
if (gmt_ptr != NULL)
zonet = difftm(&gmt, &local) / 60;
else
/* We are on a system like VMS, where the system clock is
in local time and the system has no concept of timezones.
Hopefully we can fake this out (for the case in which the
user specifies no timezone) by just saying the timezone
is zero. */
zonet = 0;
if (local.tm_isdst)
zonet += 60;
} else {
if ((tm = localtime_r(now, &local)) == NULL)
/*
* Should use the specified zone, not localtime.
* Fake it using gmtime and arithmetic.
* This is good enough because we use only the year/month/day,
* not other fields of struct tm.
*/
time_t fake = *now + (*zone * 60);
if ((tm = gmtime_r(&fake, &local)) == NULL)
return -1;
}
param.yyYear = tm->tm_year + 1900;