📄 mktime.c
字号:
/* Invert CONVERT by probing. First assume the same offset as last time. */ t0 = ydhms_diff (year, yday, hour, min, sec, EPOCH_YEAR - TM_YEAR_BASE, 0, 0, 0, - guessed_offset); if (TIME_T_MAX / INT_MAX / 366 / 24 / 60 / 60 < 3) { /* time_t isn't large enough to rule out overflows, so check for major overflows. A gross check suffices, since if t0 has overflowed, it is off by a multiple of TIME_T_MAX - TIME_T_MIN + 1. So ignore any component of the difference that is bounded by a small value. */ /* Approximate log base 2 of the number of time units per biennium. A biennium is 2 years; use this unit instead of years to avoid integer overflow. For example, 2 average Gregorian years are 2 * 365.2425 * 24 * 60 * 60 seconds, which is 63113904 seconds, and rint (log2 (63113904)) is 26. */ int ALOG2_SECONDS_PER_BIENNIUM = 26; int ALOG2_MINUTES_PER_BIENNIUM = 20; int ALOG2_HOURS_PER_BIENNIUM = 14; int ALOG2_DAYS_PER_BIENNIUM = 10; int LOG2_YEARS_PER_BIENNIUM = 1; int approx_requested_biennia = (SHR (year_requested, LOG2_YEARS_PER_BIENNIUM) - SHR (EPOCH_YEAR - TM_YEAR_BASE, LOG2_YEARS_PER_BIENNIUM) + SHR (mday, ALOG2_DAYS_PER_BIENNIUM) + SHR (hour, ALOG2_HOURS_PER_BIENNIUM) + SHR (min, ALOG2_MINUTES_PER_BIENNIUM) + (LEAP_SECONDS_POSSIBLE ? 0 : SHR (sec, ALOG2_SECONDS_PER_BIENNIUM))); int approx_biennia = SHR (t0, ALOG2_SECONDS_PER_BIENNIUM); int diff = approx_biennia - approx_requested_biennia; int abs_diff = diff < 0 ? - diff : diff; /* IRIX 4.0.5 cc miscaculates TIME_T_MIN / 3: it erroneously gives a positive value of 715827882. Setting a variable first then doing math on it seems to work. (ghazi@caip.rutgers.edu) */ time_t time_t_max = TIME_T_MAX; time_t time_t_min = TIME_T_MIN; time_t overflow_threshold = (time_t_max / 3 - time_t_min / 3) >> ALOG2_SECONDS_PER_BIENNIUM; if (overflow_threshold < abs_diff) { /* Overflow occurred. Try repairing it; this might work if the time zone offset is enough to undo the overflow. */ time_t repaired_t0 = -1 - t0; approx_biennia = SHR (repaired_t0, ALOG2_SECONDS_PER_BIENNIUM); diff = approx_biennia - approx_requested_biennia; abs_diff = diff < 0 ? - diff : diff; if (overflow_threshold < abs_diff) return -1; guessed_offset += repaired_t0 - t0; t0 = repaired_t0; } } /* Repeatedly use the error to improve the guess. */ for (t = t1 = t2 = t0, dst2 = 0; (gt = guess_time_tm (year, yday, hour, min, sec, &t, ranged_convert (convert, &t, &tm)), t != gt); t1 = t2, t2 = t, t = gt, dst2 = tm.tm_isdst != 0) if (t == t1 && t != t2 && (tm.tm_isdst < 0 || (isdst < 0 ? dst2 <= (tm.tm_isdst != 0) : (isdst != 0) != (tm.tm_isdst != 0)))) /* We can't possibly find a match, as we are oscillating between two values. The requested time probably falls within a spring-forward gap of size GT - T. Follow the common practice in this case, which is to return a time that is GT - T away from the requested time, preferring a time whose tm_isdst differs from the requested value. (If no tm_isdst was requested and only one of the two values has a nonzero tm_isdst, prefer that value.) In practice, this is more useful than returning -1. */ goto offset_found; else if (--remaining_probes == 0) return -1; /* We have a match. Check whether tm.tm_isdst has the requested value, if any. */ if (isdst != tm.tm_isdst && 0 <= isdst && 0 <= tm.tm_isdst) { /* tm.tm_isdst has the wrong value. Look for a neighboring time with the right value, and use its UTC offset. Heuristic: probe the adjacent timestamps in both directions, looking for the desired isdst. This should work for all real time zone histories in the tz database. */ /* Distance between probes when looking for a DST boundary. In tzdata2003a, the shortest period of DST is 601200 seconds (e.g., America/Recife starting 2000-10-08 01:00), and the shortest period of non-DST surrounded by DST is 694800 seconds (Africa/Tunis starting 1943-04-17 01:00). Use the minimum of these two values, so we don't miss these short periods when probing. */ int stride = 601200; /* The longest period of DST in tzdata2003a is 536454000 seconds (e.g., America/Jujuy starting 1946-10-01 01:00). The longest period of non-DST is much longer, but it makes no real sense to search for more than a year of non-DST, so use the DST max. */ int duration_max = 536454000; /* Search in both directions, so the maximum distance is half the duration; add the stride to avoid off-by-1 problems. */ int delta_bound = duration_max / 2 + stride; int delta, direction; for (delta = stride; delta < delta_bound; delta += stride) for (direction = -1; direction <= 1; direction += 2) { time_t ot = t + delta * direction; if ((ot < t) == (direction < 0)) { struct tm otm; ranged_convert (convert, &ot, &otm); if (otm.tm_isdst == isdst) { /* We found the desired tm_isdst. Extrapolate back to the desired time. */ t = guess_time_tm (year, yday, hour, min, sec, &ot, &otm); ranged_convert (convert, &t, &tm); goto offset_found; } } } } offset_found: *offset = guessed_offset + t - t0; if (LEAP_SECONDS_POSSIBLE && sec_requested != tm.tm_sec) { /* Adjust time to reflect the tm_sec requested, not the normalized value. Also, repair any damage from a false match due to a leap second. */ int sec_adjustment = (sec == 0 && tm.tm_sec == 60) - sec; t1 = t + sec_requested; t2 = t1 + sec_adjustment; if (((t1 < t) != (sec_requested < 0)) | ((t2 < t1) != (sec_adjustment < 0)) | ! convert (&t2, &tm)) return -1; t = t2; } *tp = tm; return t;}/* FIXME: This should use a signed type wide enough to hold any UTC offset in seconds. 'int' should be good enough for GNU code. We can't fix this unilaterally though, as other modules invoke __mktime_internal. */static time_t localtime_offset;/* Convert *TP to a time_t value. */time_tmktime (struct tm *tp){#ifdef _LIBC /* POSIX.1 8.1.1 requires that whenever mktime() is called, the time zone names contained in the external variable `tzname' shall be set as if the tzset() function had been called. */ __tzset ();#endif return __mktime_internal (tp, __localtime_r, &localtime_offset);}#ifdef weak_aliasweak_alias (mktime, timelocal)#endif#ifdef _LIBClibc_hidden_def (mktime)libc_hidden_weak (timelocal)#endif#if DEBUGstatic intnot_equal_tm (const struct tm *a, const struct tm *b){ return ((a->tm_sec ^ b->tm_sec) | (a->tm_min ^ b->tm_min) | (a->tm_hour ^ b->tm_hour) | (a->tm_mday ^ b->tm_mday) | (a->tm_mon ^ b->tm_mon) | (a->tm_year ^ b->tm_year) | (a->tm_yday ^ b->tm_yday) | (a->tm_isdst ^ b->tm_isdst));}static voidprint_tm (const struct tm *tp){ if (tp) printf ("%04d-%02d-%02d %02d:%02d:%02d yday %03d wday %d isdst %d", tp->tm_year + TM_YEAR_BASE, tp->tm_mon + 1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec, tp->tm_yday, tp->tm_wday, tp->tm_isdst); else printf ("0");}static intcheck_result (time_t tk, struct tm tmk, time_t tl, const struct tm *lt){ if (tk != tl || !lt || not_equal_tm (&tmk, lt)) { printf ("mktime ("); print_tm (lt); printf (")\nyields ("); print_tm (&tmk); printf (") == %ld, should be %ld\n", (long int) tk, (long int) tl); return 1; } return 0;}intmain (int argc, char **argv){ int status = 0; struct tm tm, tmk, tml; struct tm *lt; time_t tk, tl, tl1; char trailer; if ((argc == 3 || argc == 4) && (sscanf (argv[1], "%d-%d-%d%c", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &trailer) == 3) && (sscanf (argv[2], "%d:%d:%d%c", &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &trailer) == 3)) { tm.tm_year -= TM_YEAR_BASE; tm.tm_mon--; tm.tm_isdst = argc == 3 ? -1 : atoi (argv[3]); tmk = tm; tl = mktime (&tmk); lt = localtime (&tl); if (lt) { tml = *lt; lt = &tml; } printf ("mktime returns %ld == ", (long int) tl); print_tm (&tmk); printf ("\n"); status = check_result (tl, tmk, tl, lt); } else if (argc == 4 || (argc == 5 && strcmp (argv[4], "-") == 0)) { time_t from = atol (argv[1]); time_t by = atol (argv[2]); time_t to = atol (argv[3]); if (argc == 4) for (tl = from; by < 0 ? to <= tl : tl <= to; tl = tl1) { lt = localtime (&tl); if (lt) { tmk = tml = *lt; tk = mktime (&tmk); status |= check_result (tk, tmk, tl, &tml); } else { printf ("localtime (%ld) yields 0\n", (long int) tl); status = 1; } tl1 = tl + by; if ((tl1 < tl) != (by < 0)) break; } else for (tl = from; by < 0 ? to <= tl : tl <= to; tl = tl1) { /* Null benchmark. */ lt = localtime (&tl); if (lt) { tmk = tml = *lt; tk = tl; status |= check_result (tk, tmk, tl, &tml); } else { printf ("localtime (%ld) yields 0\n", (long int) tl); status = 1; } tl1 = tl + by; if ((tl1 < tl) != (by < 0)) break; } } else printf ("Usage:\\t%s YYYY-MM-DD HH:MM:SS [ISDST] # Test given time.\n\\t%s FROM BY TO # Test values FROM, FROM+BY, ..., TO.\n\\t%s FROM BY TO - # Do not test those values (for benchmark).\n", argv[0], argv[0], argv[0]); return status;}#endif /* DEBUG *//*Local Variables:compile-command: "gcc -DDEBUG -Wall -W -O -g mktime.c -o mktime"End:*/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -