Commit 27bebb79 authored by Robert Sprowson's avatar Robert Sprowson
Browse files

Make mktime() correct for local time.

Following an earlier fix, the logic of mktime() has turned out to need reviewing too.
To summarise
  time() -> a UTC time in seconds
  gmtime() -> breaks down a UTC time into components
  localtime() -> breaks down a UTC time into local components (tz + DST)
  mktime() -> converts local components back into UTC time, normalising
In mktime(), the current locale (via setlocale()) is considered and used to choose the timezone, assuming an appropriate territory module is loaded.
The value of the tm_isdst field is factored into the offset calculation.
However, mktime() is also defined as normalising the components of time and updating the caller's block with normalised values, in local time, so the local offset is reapplied at the end.

Moved time_to_tm up in the function so mktime() can use it.
Corrected/lined up some comments.
Also, mktime() no longer forces tm_isdst to -1, it preserves the user's value, so consecutive mktime()s are consistent.
Added test to "test/tzones.c", results compared with Windows XP.

Version 5.69. Tagged as 'RISC_OSLib-5_69'
parent 9fab6d08
......@@ -11,13 +11,13 @@
GBLS Module_HelpVersion
GBLS Module_ComponentName
GBLS Module_ComponentPath
Module_MajorVersion SETS "5.68"
Module_Version SETA 568
Module_MajorVersion SETS "5.69"
Module_Version SETA 569
Module_MinorVersion SETS ""
Module_Date SETS "28 May 2012"
Module_ApplicationDate SETS "28-May-12"
Module_Date SETS "11 Jun 2012"
Module_ApplicationDate SETS "11-Jun-12"
Module_ComponentName SETS "RISC_OSLib"
Module_ComponentPath SETS "castle/RiscOS/Sources/Lib/RISC_OSLib"
Module_FullVersion SETS "5.68"
Module_HelpVersion SETS "5.68 (28 May 2012)"
Module_FullVersion SETS "5.69"
Module_HelpVersion SETS "5.69 (11 Jun 2012)"
END
/* (5.68)
/* (5.69)
*
* This file is automatically maintained by srccommit, do not edit manually.
* Last processed by srccommit version: 1.1.
*
*/
#define Module_MajorVersion_CMHG 5.68
#define Module_MajorVersion_CMHG 5.69
#define Module_MinorVersion_CMHG
#define Module_Date_CMHG 28 May 2012
#define Module_Date_CMHG 11 Jun 2012
#define Module_MajorVersion "5.68"
#define Module_Version 568
#define Module_MajorVersion "5.69"
#define Module_Version 569
#define Module_MinorVersion ""
#define Module_Date "28 May 2012"
#define Module_Date "11 Jun 2012"
#define Module_ApplicationDate "28-May-12"
#define Module_ApplicationDate "11-Jun-12"
#define Module_ComponentName "RISC_OSLib"
#define Module_ComponentPath "castle/RiscOS/Sources/Lib/RISC_OSLib"
#define Module_FullVersion "5.68"
#define Module_HelpVersion "5.68 (28 May 2012)"
#define Module_LibraryVersionInfo "5:68"
#define Module_FullVersion "5.69"
#define Module_HelpVersion "5.69 (11 Jun 2012)"
#define Module_LibraryVersionInfo "5:69"
......@@ -68,15 +68,45 @@ static int tm_carry(int *a, int b, int q)
return (hi << 16) + lo;
}
static struct tm *time_to_tm(struct tm *_tms, time_t t, int dst)
{
int i = 0, yr;
/* unix time already in seconds (since 1-Jan-1970) ... */
_tms->tm_sec = t % 60; t /= 60;
_tms->tm_min = t % 60; t /= 60;
_tms->tm_hour = t % 24; t /= 24;
/* The next line converts *timer arg into days since 1-Jan-1900 from t which
now holds days since 1-Jan-1970. Now there are really only 17 leap years
in this range 04,08,...,68 but we use 18 so that we do not have to do
special case code for 1900 which was not a leap year. Of course this
cannot give problems as pre-1970 times are not representable in *timer. */
t += 70*365 + 18;
_tms->tm_wday = t % 7; /* it just happens to be so */
yr = 4 * (t / (365*4+1)); t %= (365*4+1);
if (t >= 366) yr += (t-1) / 365, t = (t-1) % 365;
_tms->tm_year = yr; /* Add in magic timebase */
_tms->tm_yday = t;
if ((yr & 3) != 0 && t >= 31+28) t++;
while (t >= monlen[i]) t -= monlen[i++];
_tms->tm_mday = t+1;
_tms->tm_mon = i;
_tms->tm_isdst = dst;
return _tms;
}
time_t mktime(struct tm *timeptr)
{ /* the Oct 1986 ANSI draft spec allows ANY values for the contents */
/* of timeptr. This leave the question - what is month -9 or +123? */
/* the code below resolves it in one way: */
{ /* ISO9899 7.23.2.3 (2) says that the components may take ANY values */
/* and that somehow mktime() should normalise these into a form that */
/* is in range. This leaves the question - what is month -9 or +123? */
/* the code below resolves by normalising from seconds upwards, */
/* propagating any carries up to the year component then checking for */
/* overflow. */
/* Also note that struct tm is allowed to have signed values in it for */
/* the purposes of this function even though normalized times all have */
/* just positive entries. */
time_t t;
int w, v, yday;
int w, v, yday, offset, territory;
int sec = timeptr->tm_sec;
int min = timeptr->tm_min;
int hour = timeptr->tm_hour;
......@@ -84,25 +114,43 @@ time_t mktime(struct tm *timeptr)
int mon = timeptr->tm_mon;
int year = timeptr->tm_year;
int quadyear = 0;
/* The next line applies a simple test that detects some gross overflows */
_kernel_swi_regs r;
/* The next line is a simple test that detects some gross overflows */
if (year > 0x40000000 || year < -0x40000000) return (time_t)-1;
/* Work out what the timezone/DST correction is */
territory = __locales[N_LC_TIME];
if (!territory) {
r.r[0] = -1; /* If C locale use current configured territory */
} else {
r.r[0] = TERRITORY_EXTRACT(territory);
r.r[1] = TERRITORY_TZ_EXTRACT(territory);
r.r[4] = TERRITORY_TZ_API_EXT; /* If not supported, never mind */
}
if (_kernel_swi(Territory_ReadTimeZones, &r, &r) == NULL) {
offset = (timeptr->tm_isdst > 0) ? r.r[3] : r.r[2];
offset = offset / 100; /* centiseconds -> seconds */
} else {
offset = 0;
}
/* we really do have to propagate carries up it seems */
/* careful about overflow for divide, but not carry add. */
w = tm_carry(&sec,0,60); /* leaves 0 <= sec < 60 */
w = tm_carry(&min,w,60); /* leaves 0 <= min < 60 */
w = tm_carry(&hour,w,24); /* leaves 0 <= hour < 24 */
w = tm_carry(&sec,-offset,60); /* leaves 0 <= sec < 60 */
w = tm_carry(&min,w,60); /* leaves 0 <= min < 60 */
w = tm_carry(&hour,w,24); /* leaves 0 <= hour < 24 */
quadyear = tm_carry(&mday,w - 1,(4*365+1)); /* 0 <= mday < 4 years */
/* The next line can not possibly result in year overflowing since the */
/* initial values was checked earlier and the month can only cause a */
/* carry of size up to MAXINT/12 with quadyear limited to MAXINT/365. */
/* The next line can not possibly result in year overflowing since the */
/* initial value was checked earlier and the month can only cause a */
/* carry of size up to MAXINT/12 with quadyear limited to MAXINT/365. */
year += quadyear*4 + tm_carry(&mon,0,12);
/* at last the mday is in 0..4*365 and the mon in 0..11 */
#define notleapyear(year) (((year) & 3)!=0)
/* Note that 1900 is not in the range of valid dates and so I will fudge */
/* the issue about it not being a leap year. */
/* Note that 1900 is not in the range of valid dates and so I will */
/* fudge the issue about it not being a leap year. */
while (mday >= monlen[mon])
{ mday -= monlen[mon++];
......@@ -123,14 +171,14 @@ time_t mktime(struct tm *timeptr)
v = (365*4+1)*(year/4) + 365*(year & 3) + yday;
if (!notleapyear(year)) v--;
/* v is now the number of days since 1 Jan 1900, and I have subtracted a */
/* sly 1 to adjust for 1900 not being a leap year. */
/* v is now the number of days since 1 Jan 1900, and I have subtracted */
/* a sly 1 to adjust for 1900 not being a leap year. */
#undef notleapyear
/* Adjust for a base at 1 Jan 1970 */
/* Adjust for a base at 1 Jan 1970 which is 17 leap years since 1900 */
#define DAYS (17*(365*4+1)+2*365)
#define DAYS ((70*365)+17)
t = min + 60*(hour + 24*(v - DAYS));
#undef DAYS
{ int thi = ((int)t >> 16)*60;
......@@ -140,18 +188,15 @@ time_t mktime(struct tm *timeptr)
if ((thi & 0xffff0000) != 0) return (time_t)-1;
}
timeptr->tm_sec = sec;
timeptr->tm_min = min;
timeptr->tm_hour = hour;
timeptr->tm_mday = mday + 1;
timeptr->tm_mon = mon;
timeptr->tm_year = year;
timeptr->tm_wday = (v + 1) % 7;
timeptr->tm_yday = yday;
timeptr->tm_isdst = -1; /* unavailable */
/* Update the local time block by reapplying timezone/DST */
{ long long sum;
sum = t + (long long)offset;
if ((time_t)sum != sum) return (time_t)-1;
time_to_tm(timeptr, (time_t)sum, timeptr->tm_isdst);
}
return t;
/* Now I know why Unix didn't have this */
/* Now I know why Unix didn't have this */
}
char *asctime(const struct tm *timeptr)
......@@ -169,33 +214,6 @@ char *ctime(const time_t *timer)
{ return asctime(localtime(timer));
}
static struct tm *time_to_tm(struct tm *_tms, time_t t, int dst)
{
int i = 0, yr;
/* unix time already in seconds (since 1-Jan-1970) ... */
_tms->tm_sec = t % 60; t /= 60;
_tms->tm_min = t % 60; t /= 60;
_tms->tm_hour = t % 24; t /= 24;
/* The next line converts *timer arg into days since 1-Jan-1900 from t which
now holds days since 1-Jan-1970. Now there are really only 17 leap years
in this range 04,08,...,68 but we use 18 so that we do not have to do
special case code for 1900 which was not a leap year. Of course this
cannot give problems as pre-1970 times are not representable in *timer. */
t += 70*365 + 18;
_tms->tm_wday = t % 7; /* it just happens to be so */
yr = 4 * (t / (365*4+1)); t %= (365*4+1);
if (t >= 366) yr += (t-1) / 365, t = (t-1) % 365;
_tms->tm_year = yr; /* Add in magic timebase */
_tms->tm_yday = t;
if ((yr & 3) != 0 && t >= 31+28) t++;
while (t >= monlen[i]) t -= monlen[i++];
_tms->tm_mday = t+1;
_tms->tm_mon = i;
_tms->tm_isdst = dst;
return _tms;
}
struct tm *gmtime(const time_t *timer)
{
static struct tm _tms;
......@@ -231,7 +249,7 @@ struct tm *localtime(const time_t *timer)
dst = -1;
v = _kernel_osbyte(161, 220 /* AlarmAndTimeCMOS */, 0);
if (v >= 0)
dst = dst & 0x8000;
dst = v & 0x8000;
territory = __locales[N_LC_TIME];
if (!territory) {
......
......@@ -63,5 +63,8 @@ int main(void)
strftime(buffer, 256, "%x %X %z %Z", local);
printf("in this program's locale %s\n", buffer);
/* Check for reciprocity */
if (now != mktime(localtime(&now))) printf("Asymmetry in mktime()!\n");
return 0;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment