New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Possible bug using POSIX::strftime Digital UNIX Perl 5.005_03 #514
Comments
From phil@finchcomputer.comI believe I have found a bug in Perl > 5.004_04 (possibly only Perl The problem seems to be that strftime C module calls mktime() which Should I not use strftime? Is strftime supposed to call mktime? It The *real* world where I've run into problems, gmtime sets tm_isdst to % /opt/dtv/bin/perl -e '@tmp=gmtime;use POSIX; print scalar(gmtime), % /opt/dtv/bin/perl -e '@tmp=gmtime;use POSIX; print scalar(gmtime), The equivalent C code that works without this problem. Details, including output from perlbug below, and I believe I've found Phil Details... * The system (Digital UNIX 4, Perl 5.005_03): * The problematic type of Perl program (NOTE: note 1hr difference!): * Sample C code that works... * Output from the system date call and from the C code. The first time % date -u * POSSIBLE FIX: Output from diff directory is perl5.005_03/ext/POSIX... * bambam% /opt/dtv/perl/5.005_03/bin/perlbug -d -v Configured by lobbesp at Mon Jul 19 10:56:06 PDT 1999. Summary of my perl5 (5.0 patchlevel 5 subversion 3) configuration: Locally applied patches: @INC for perl 5.00503: Environment for perl 5.00503: Complete configuration data for perl 5.00503: Author='' |
From [Unknown Contact. See original ticket]On Sun, 19 Sep 1999 15:30:31 -0700, Gurusamy Sarathy wrote (in part): Sarathy> The mktime() call was added (change#1914) to fix the Sarathy> I'd say this needs to be controlled via the hints only I know of no vendor strftime() which doesn't need help to avoid Sometime today I'll submit a patch which substitutes a /spider |
From @timbunceOn Thu, Sep 23, 1999 at 09:55:26AM -0400, spider-perl@Orb.Nashua.NH.US wrote:
Random observation: I've found that mktime() can be _very slow, so Tim. |
From [Unknown Contact. See original ticket]On Thu, 23 Sep 1999 16:14:30 +0100, Tim Bunce wrote (in part): Tim> Random observation: I've found that mktime() can be _very The mini-mktime won't be doing anything with tzset() or its /spider |
From [Unknown Contact. See original ticket]Hi, Just to clarify things a bit, I believe taking out mktime in this case I'd personally have my program SEGV if I pass improper data to Maybe we should add a test to POSIX (not sure how portable this is but use POSIX; Phil
spider> On Sun, 19 Sep 1999 15:30:31 -0700, Gurusamy Sarathy wrote (in Sarathy> I'd say this needs to be controlled via the hints only for spider> I know of no vendor strftime() which doesn't need help to spider> Sometime today I'll submit a patch which substitutes a spider> /spider |
From [Unknown Contact. See original ticket]On Thu, 23 Sep 1999 09:55:26 -0400, I wrote (in part): sb> Sometime today I'll submit a patch which substitutes a Here it is. /spider Inline Patch--- ext/POSIX/POSIX.pod.DIST Tue Jul 20 13:17:56 1999
+++ ext/POSIX/POSIX.pod Thu Sep 23 17:47:51 1999
@@ -1024,7 +1024,8 @@ If you want your code to be portable, yo
should use only the conversion specifiers defined by the ANSI C
standard. These are C<aAbBcdHIjmMpSUwWxXyYZ%>.
The given arguments are made consistent
-by calling C<mktime()> before calling your system's C<strftime()> function.
+as though by calling C<mktime()> before calling your system's
+C<strftime()> function, except that the C<isdst> value is not affected.
The string for Tuesday, December 12, 1995.
--- ext/POSIX/POSIX.xs.DIST Fri Aug 20 11:51:30 1999
+++ ext/POSIX/POSIX.xs Thu Sep 23 15:39:30 1999
@@ -332,6 +332,196 @@ init_tm(struct tm *ptm) /* see mktime,
# define init_tm(ptm)
#endif
+/*
+ * mini_mktime - normalise struct tm values without the localtime()
+ * semantics (and overhead) of mktime().
+ */
+static void
+mini_mktime(struct tm *ptm)
+{
+ int yearday;
+ int secs;
+ int month, mday, year, jday;
+ int odd_cent, odd_year;
+
+#define DAYS_PER_YEAR 365
+#define DAYS_PER_QYEAR (4*DAYS_PER_YEAR+1)
+#define DAYS_PER_CENT (25*DAYS_PER_QYEAR-1)
+#define DAYS_PER_QCENT (4*DAYS_PER_CENT+1)
+#define SECS_PER_HOUR (60*60)
+#define SECS_PER_DAY (24*SECS_PER_HOUR)
+/* parentheses deliberately absent on these two, otherwise they don't work */
+#define MONTH_TO_DAYS 153/5
+#define DAYS_TO_MONTH 5/153
+/* offset to bias by March (month 4) 1st between month/mday & year finding */
+#define YEAR_ADJUST (4*MONTH_TO_DAYS+1)
+/* as used here, the algorithm leaves Sunday as day 1 unless we adjust it */
+#define WEEKDAY_BIAS 6 /* (1+6)%7 makes Sunday 0 again */
+
+/*
+ * Year/day algorithm notes:
+ *
+ * With a suitable offset for numeric value of the month, one can find
+ * an offset into the year by considering months to have 30.6 (153/5) days,
+ * using integer arithmetic (i.e., with truncation). To avoid too much
+ * messing about with leap days, we consider January and February to be
+ * the 13th and 14th month of the previous year. After that transformation,
+ * we need the month index we use to be high by 1 from 'normal human' usage,
+ * so the month index values we use run from 4 through 15.
+ *
+ * Given that, and the rules for the Gregorian calendar (leap years are those
+ * divisible by 4 unless also divisible by 100, when they must be divisible
+ * by 400 instead), we can simply calculate the number of days since some
+ * arbitrary 'beginning of time' by futzing with the (adjusted) year number,
+ * the days we derive from our month index, and adding in the day of the
+ * month. The value used here is not adjusted for the actual origin which
+ * it normally would use (1 January A.D. 1), since we're not exposing it.
+ * We're only building the value so we can turn around and get the
+ * normalised values for the year, month, day-of-month, and day-of-year.
+ *
+ * For going backward, we need to bias the value we're using so that we find
+ * the right year value. (Basically, we don't want the contribution of
+ * March 1st to the number to apply while deriving the year). Having done
+ * that, we 'count up' the contribution to the year number by accounting for
+ * full quadracenturies (400-year periods) with their extra leap days, plus
+ * the contribution from full centuries (to avoid counting in the lost leap
+ * days), plus the contribution from full quad-years (to count in the normal
+ * leap days), plus the leftover contribution from any non-leap years.
+ * At this point, if we were working with an actual leap day, we'll have 0
+ * days left over. This is also true for March 1st, however. So, we have
+ * to special-case that result, and (earlier) keep track of the 'odd'
+ * century and year contributions. If we got 4 extra centuries in a qcent,
+ * or 4 extra years in a qyear, then it's a leap day and we call it 29 Feb.
+ * Otherwise, we add back in the earlier bias we removed (the 123 from
+ * figuring in March 1st), find the month index (integer division by 30.6),
+ * and the remainder is the day-of-month. We then have to convert back to
+ * 'real' months (including fixing January and February from being 14/15 in
+ * the previous year to being in the proper year). After that, to get
+ * tm_yday, we work with the normalised year and get a new yearday value for
+ * January 1st, which we subtract from the yearday value we had earlier,
+ * representing the date we've re-built. This is done from January 1
+ * because tm_yday is 0-origin.
+ *
+ * Since POSIX time routines are only guaranteed to work for times since the
+ * UNIX epoch (00:00:00 1 Jan 1970 UTC), the fact that this algorithm
+ * applies Gregorian calendar rules even to dates before the 16th century
+ * doesn't bother me. Besides, you'd need cultural context for a given
+ * date to know whether it was Julian or Gregorian calendar, and that's
+ * outside the scope for this routine. Since we convert back based on the
+ * same rules we used to build the yearday, you'll only get strange results
+ * for input which needed normalising, or for the 'odd' century years which
+ * were leap years in the Julian calander but not in the Gregorian one.
+ * I can live with that.
+ *
+ * This algorithm also fails to handle years before A.D. 1 gracefully, but
+ * that's still outside the scope for POSIX time manipulation, so I don't
+ * care.
+ */
+
+ year = 1900 + ptm->tm_year;
+ month = ptm->tm_mon;
+ mday = ptm->tm_mday;
+ /* allow given yday with no month & mday to dominate the result */
+ if (ptm->tm_yday >= 0 && mday <= 0 && month <= 0) {
+ month = 0;
+ mday = 0;
+ jday = 1 + ptm->tm_yday;
+ }
+ else {
+ jday = 0;
+ }
+ if (month >= 2)
+ month+=2;
+ else
+ month+=14, year--;
+ yearday = DAYS_PER_YEAR * year + year/4 - year/100 + year/400;
+ yearday += month*MONTH_TO_DAYS + mday + jday;
+ /*
+ * Note that we don't know when leap-seconds were or will be,
+ * so we have to trust the user if we get something which looks
+ * like a sensible leap-second. Wild values for seconds will
+ * be rationalised, however.
+ */
+ if ((unsigned) ptm->tm_sec <= 60) {
+ secs = 0;
+ }
+ else {
+ secs = ptm->tm_sec;
+ ptm->tm_sec = 0;
+ }
+ secs += 60 * ptm->tm_min;
+ secs += SECS_PER_HOUR * ptm->tm_hour;
+ if (secs < 0) {
+ if (secs-(secs/SECS_PER_DAY*SECS_PER_DAY) < 0) {
+ /* got negative remainder, but need positive time */
+ /* back off an extra day to compensate */
+ yearday += (secs/SECS_PER_DAY)-1;
+ secs -= SECS_PER_DAY * (secs/SECS_PER_DAY - 1);
+ }
+ else {
+ yearday += (secs/SECS_PER_DAY);
+ secs -= SECS_PER_DAY * (secs/SECS_PER_DAY);
+ }
+ }
+ else if (secs >= SECS_PER_DAY) {
+ yearday += (secs/SECS_PER_DAY);
+ secs %= SECS_PER_DAY;
+ }
+ ptm->tm_hour = secs/SECS_PER_HOUR;
+ secs %= SECS_PER_HOUR;
+ ptm->tm_min = secs/60;
+ secs %= 60;
+ ptm->tm_sec += secs;
+ /* done with time of day effects */
+ /*
+ * The algorithm for yearday has (so far) left it high by 428.
+ * To avoid mistaking a legitimate Feb 29 as Mar 1, we need to
+ * bias it by 123 while trying to figure out what year it
+ * really represents. Even with this tweak, the reverse
+ * translation fails for years before A.D. 0001.
+ * It would still fail for Feb 29, but we catch that one below.
+ */
+ jday = yearday; /* save for later fixup vis-a-vis Jan 1 */
+ yearday -= YEAR_ADJUST;
+ year = (yearday / DAYS_PER_QCENT) * 400;
+ yearday %= DAYS_PER_QCENT;
+ odd_cent = yearday / DAYS_PER_CENT;
+ year += odd_cent * 100;
+ yearday %= DAYS_PER_CENT;
+ year += (yearday / DAYS_PER_QYEAR) * 4;
+ yearday %= DAYS_PER_QYEAR;
+ odd_year = yearday / DAYS_PER_YEAR;
+ year += odd_year;
+ yearday %= DAYS_PER_YEAR;
+ if (!yearday && (odd_cent==4 || odd_year==4)) { /* catch Feb 29 */
+ month = 1;
+ yearday = 29;
+ }
+ else {
+ yearday += YEAR_ADJUST; /* recover March 1st crock */
+ month = yearday*DAYS_TO_MONTH;
+ yearday -= month*MONTH_TO_DAYS;
+ /* recover other leap-year adjustment */
+ if (month > 13) {
+ month-=14;
+ year++;
+ }
+ else {
+ month-=2;
+ }
+ }
+ ptm->tm_year = year - 1900;
+ ptm->tm_mon = month;
+ ptm->tm_mday = yearday;
+ /* re-build yearday based on Jan 1 to get tm_yday */
+ year--;
+ yearday = year*DAYS_PER_YEAR + year/4 - year/100 + year/400;
+ yearday += 14*MONTH_TO_DAYS + 1;
+ ptm->tm_yday = jday - yearday;
+ /* fix tm_wday if not overridden by caller */
+ if ((unsigned)ptm->tm_wday > 6)
+ ptm->tm_wday = (jday + WEEKDAY_BIAS) % 7;
+}
#ifdef HAS_LONG_DOUBLE
# if LONG_DOUBLESIZE > DOUBLESIZE
@@ -3650,7 +3840,7 @@ strftime(fmt, sec, min, hour, mday, mon,
mytm.tm_wday = wday;
mytm.tm_yday = yday;
mytm.tm_isdst = isdst;
- (void) mktime(&mytm);
+ mini_mktime(&mytm);
len = strftime(tmpbuf, sizeof tmpbuf, fmt, &mytm);
/*
** The following is needed to handle to the situation where
--- t/lib/posix.t.DIST Tue Jul 20 13:18:13 1999
+++ t/lib/posix.t Thu Sep 23 17:00:43 1999
@@ -14,7 +14,7 @@
use strict subs;
$| = 1;
-print "1..18\n";
+print "1..26\n";
$Is_W32 = $^O eq 'MSWin32';
@@ -94,6 +94,31 @@
# -DSTRUCT_TM_HASZONE to your cflags when compiling ext/POSIX/POSIX.c.
# See ext/POSIX/hints/sunos_4.pl and ext/POSIX/hints/linux.pl
print POSIX::strftime("ok 18 # %H:%M, on %D\n", localtime());
+
+# If that worked, validate the mini_mktime() routine's normalisation of
+# input fields to strftime().
+sub try_strftime {
+ my $num = shift;
+ my $expect = shift;
+ my $got = POSIX::strftime("%a %b %d %H:%M:%S %Y %j", @_);
+ if ($got eq $expect) {
+ print "ok $num\n";
+ }
+ else {
+ print "# expected: $expect\n# got: $got\nnot ok $num\n";
+ }
+}
+
+$lc = &POSIX::setlocale(&POSIX::LC_TIME, 'C') if $Config{d_setlocale};
+try_strftime(19, "Wed Feb 28 00:00:00 1996 059", 0,0,0, 28,1,96);
+try_strftime(20, "Thu Feb 29 00:00:60 1996 060", 60,0,-24, 30,1,96);
+try_strftime(21, "Fri Mar 01 00:00:00 1996 061", 0,0,-24, 31,1,96);
+try_strftime(22, "Sun Feb 28 00:00:00 1999 059", 0,0,0, 28,1,99);
+try_strftime(23, "Mon Mar 01 00:00:00 1999 060", 0,0,24, 28,1,99);
+try_strftime(24, "Mon Feb 28 00:00:00 2000 059", 0,0,0, 28,1,100);
+try_strftime(25, "Tue Feb 29 00:00:00 2000 060", 0,0,0, 0,2,100);
+try_strftime(26, "Wed Mar 01 00:00:00 2000 061", 0,0,0, 1,2,100);
+&POSIX::setlocale(&POSIX::LC_TIME, $lc) if $Config{d_setlocale};
$| = 0;
# The following line assumes buffered output, which may be not true with EMX: |
From @gsarOn Mon, 13 Sep 1999 14:51:17 PDT, Phil Lobbes wrote:
The mktime() call was added (change#1914) to fix the behavior of http://www.xray.mpe.mpg.de/mailing-lists/perl5-porters/1998-09/msg01660.html I'd say this needs to be controlled via the hints only for the I've checked in the attached change. Someone will need to add a hints
Sarathy Inline Patch-----------------------------------8<-----------------------------------
Change 4196 by gsar@auger on 1999/09/19 22:14:29
control change#1914 via hints (causes problems on some platforms)
Affected files ...
... //depot/perl/ext/POSIX/POSIX.pod#12 edit
... //depot/perl/ext/POSIX/POSIX.xs#49 edit
... //depot/perl/ext/POSIX/hints/linux.pl#3 edit
Differences ...
==== //depot/perl/ext/POSIX/POSIX.pod#12 (text) ====
Index: perl/ext/POSIX/POSIX.pod
--- perl/ext/POSIX/POSIX.pod.~1~ Sun Sep 19 15:14:34 1999
+++ perl/ext/POSIX/POSIX.pod Sun Sep 19 15:14:34 1999
@@ -1023,7 +1023,7 @@
If you want your code to be portable, your format (C<fmt>) argument
should use only the conversion specifiers defined by the ANSI C
standard. These are C<aAbBcdHIjmMpSUwWxXyYZ%>.
-The given arguments are made consistent
+On platforms that need it, the given arguments are made consistent
by calling C<mktime()> before calling your system's C<strftime()> function.
The string for Tuesday, December 12, 1995.
==== //depot/perl/ext/POSIX/POSIX.xs#49 (text) ====
Index: perl/ext/POSIX/POSIX.xs
--- perl/ext/POSIX/POSIX.xs.~1~ Sun Sep 19 15:14:34 1999
+++ perl/ext/POSIX/POSIX.xs Sun Sep 19 15:14:34 1999
@@ -3652,7 +3652,9 @@
mytm.tm_wday = wday;
mytm.tm_yday = yday;
mytm.tm_isdst = isdst;
+#if defined(HINT_STRFTIME_NEEDS_MKTIME)
(void) mktime(&mytm);
+#endif
len = strftime(tmpbuf, sizeof tmpbuf, fmt, &mytm);
/*
** The following is needed to handle to the situation where
==== //depot/perl/ext/POSIX/hints/linux.pl#3 (text) ====
Index: perl/ext/POSIX/hints/linux.pl
--- perl/ext/POSIX/hints/linux.pl.~1~ Sun Sep 19 15:14:34 1999
+++ perl/ext/POSIX/hints/linux.pl Sun Sep 19 15:14:34 1999
@@ -2,4 +2,6 @@
# Thanks to Bart Schuller <schuller@Lunatech.com>
# See Message-ID: <19971009002636.50729@tanglefoot>
# XXX A Configure test is needed.
-$self->{CCFLAGS} = $Config{ccflags} . ' -DSTRUCT_TM_HASZONE -DHINT_SC_EXIST' ;
+$self->{CCFLAGS} = $Config{ccflags}
+ . ' -DHINT_STRFTIME_NEEDS_MKTIME'
+ . ' -DSTRUCT_TM_HASZONE -DHINT_SC_EXIST' ;
End of Patch. |
Migrated from rt.perl.org#1361 (status was 'resolved')
Searchable as RT1361$
The text was updated successfully, but these errors were encountered: