summaryrefslogtreecommitdiff
path: root/ext/POSIX/POSIX.xs
diff options
context:
space:
mode:
authorNick Ing-Simmons <nik@tiuk.ti.com>1999-10-02 11:11:44 +0000
committerNick Ing-Simmons <nik@tiuk.ti.com>1999-10-02 11:11:44 +0000
commit33d8f7c1afe01d04914301ea81b4771b23fc2612 (patch)
tree72cb8a4a39d47dedec2343de539792defccac948 /ext/POSIX/POSIX.xs
parent5bc28da93666e223bb56098f72517273bc8bcbf9 (diff)
parent9741dab02becad0550bba7d5ca9e59f8ac608b2d (diff)
downloadperl-33d8f7c1afe01d04914301ea81b4771b23fc2612.tar.gz
Incremental merge of mainline
p4raw-id: //depot/utfperl@4286
Diffstat (limited to 'ext/POSIX/POSIX.xs')
-rw-r--r--ext/POSIX/POSIX.xs192
1 files changed, 191 insertions, 1 deletions
diff --git a/ext/POSIX/POSIX.xs b/ext/POSIX/POSIX.xs
index 16217f0936..23c38b5e20 100644
--- a/ext/POSIX/POSIX.xs
+++ b/ext/POSIX/POSIX.xs
@@ -332,6 +332,196 @@ init_tm(struct tm *ptm) /* see mktime, strftime and asctime */
# 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
@@ -3652,7 +3842,7 @@ strftime(fmt, sec, min, hour, mday, mon, year, wday = -1, yday = -1, isdst = -1)
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