📄 time.c
字号:
/* Copyright (C) 2002-2004 Manuel Novoa III <mjn3@codepoet.org> * * GNU Library General Public License (LGPL) version 2 or later. * * Dedicated to Toni. See uClibc/DEDICATION.mjn3 for details. *//* June 15, 2002 Initial Notes: * * Note: It is assumed throught that time_t is either long or unsigned long. * Similarly, clock_t is assumed to be long int. * * Warning: Assumptions are made about the layout of struct tm! It is * assumed that the initial fields of struct tm are (in order): * tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year, tm_wday, tm_yday * * Reached the inital goal of supporting the ANSI/ISO C99 time functions * as well as SUSv3's strptime. All timezone info is obtained from the * TZ env variable. * * Differences from glibc worth noting: * * Leap seconds are not considered here. * * glibc stores additional timezone info the struct tm, whereas we don't. * * Alternate digits and era handling are not currently implemented. * The modifiers are accepted, and tested for validity with the following * specifier, but are ignored otherwise. * * strftime does not implement glibc extension modifiers or widths for * conversion specifiers. However it does implement the glibc * extension specifiers %l, %k, and %s. It also recognizes %P, but * treats it as a synonym for %p; i.e. doesn't convert to lower case. * * strptime implements the glibc extension specifiers. However, it follows * SUSv3 in requiring at least one non-alphanumeric char between * conversion specifiers. Also, strptime only sets struct tm fields * for which format specifiers appear and does not try to infer other * fields (such as wday) as glibc's version does. * * TODO - Since glibc's %l and %k can space-pad their output in strftime, * it might be reasonable to eat whitespace first for those specifiers. * This could be done by pushing " %I" and " %H" respectively so that * leading whitespace is consumed. This is really only an issue if %l * or %k occurs at the start of the format string. * * TODO - Implement getdate? tzfile? struct tm extensions? * * TODO - Rework _time_mktime to remove the dependency on long long. *//* Oct 28, 2002 * * Fixed allowed char check for std and dst TZ fields. * * Added several options concerned with timezone support. The names will * probably change once Erik gets the new config system in place. * * Defining __TIME_TZ_FILE causes tzset() to attempt to read the TZ value * from the file /etc/TZ if the TZ env variable isn't set. The file contents * must be the intended value of TZ, followed by a newline. No other chars, * spacing, etc is allowed. As an example, an easy way for me to init * /etc/TZ appropriately would be: echo CST6CDT > /etc/TZ * * Defining __TIME_TZ_FILE_ONCE will cause all further accesses of /etc/TZ * to be skipped once a legal value has been read. * * Defining __TIME_TZ_OPT_SPEED will cause a tzset() to keep a copy of the * last TZ setting string and do a "fast out" if the current string is the * same. * * Nov 21, 2002 Fix an error return case in _time_mktime. * * Nov 26, 2002 Fix bug in setting daylight and timezone when no (valid) TZ. * Bug reported by Arne Bernin <arne@alamut.de> in regards to freeswan. * * July 27, 2003 Adjust the struct tm extension field support. * Change __tm_zone back to a ptr and add the __tm_tzname[] buffer for * __tm_zone to point to. This gets around complaints from g++. * Who knows... it might even fix the PPC timezone init problem. * * July 29, 2003 Fix a bug in mktime behavior when tm_isdst was -1. * Bug reported by "Sid Wade" <sid@vivato.net> in regards to busybox. * * NOTE: uClibc mktime behavior is different than glibc's when * the struct tm has tm_isdst == -1 and also had fields outside of * the normal ranges. * * Apparently, glibc examines (at least) tm_sec and guesses the app's * intention of assuming increasing or decreasing time when entering an * ambiguous time period at the dst<->st boundaries. * * The uClibc behavior is to always normalize the struct tm and then * try to determing the dst setting. * * As long as tm_isdst != -1 or the time specifiec by struct tm is * unambiguous (not falling in the dst<->st transition region) both * uClibc and glibc should produce the same result for mktime. * * Oct 31, 2003 Kill the seperate __tm_zone and __tm_tzname[] and which * doesn't work if you want the memcpy the struct. Sigh... I didn't * think about that. So now, when the extensions are enabled, we * malloc space when necessary and keep the timezone names in a linked * list. * * Fix a dst-related bug which resulted in use of uninitialized data. * * Nov 15, 2003 I forgot to update the thread locking in the last dst fix. * * Dec 14, 2003 Fix some dst issues in _time_mktime(). * Normalize the tm_isdst value to -1, 0, or 1. * If no dst for this timezone, then reset tm_isdst to 0. * * May 7, 2004 * Change clock() to allow wrapping. * Add timegm() function. * Make lookup_tzname() static (as it should have been). * Have strftime() get timezone information from the passed struct * for the %z and %Z conversions when using struct tm extensions. */#define _GNU_SOURCE#include <stdio.h>#include <stdlib.h>#include <stddef.h>#include <string.h>#include <time.h>#include <limits.h>#include <assert.h>#include <errno.h>#include <ctype.h>#include <langinfo.h>#include <locale.h>#include <bits/uClibc_uintmaxtostr.h>#ifdef __UCLIBC_HAS_XLOCALE__#include <xlocale.h>#endif#ifndef __isleap#define __isleap(y) ( !((y) % 4) && ( ((y) % 100) || !((y) % 400) ) )#endif#ifndef TZNAME_MAX#define TZNAME_MAX _POSIX_TZNAME_MAX#endif/**********************************************************************//* The era code is currently unfinished. *//* #define ENABLE_ERA_CODE */#define TZ_BUFLEN (2*TZNAME_MAX + 56)#ifdef __UCLIBC_HAS_TZ_FILE__#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include "paths.h"/* ":<tzname>+hh:mm:ss<tzname>+hh:mm:ss,Mmm.w.d/hh:mm:ss,Mmm.w.d/hh:mm:ss" + nul *//* 1 + 2*(1+TZNAME_MAX+1 + 9 + 7 + 9) + 1 = 2*TZNAME_MAX + 56 */#else /* __UCLIBC_HAS_TZ_FILE__ *//* Probably no longer needed. */#undef __UCLIBC_HAS_TZ_FILE_READ_MANY__#endif /* __UCLIBC_HAS_TZ_FILE__ *//**********************************************************************/extern struct tm __time_tm;typedef struct { long gmt_offset; long dst_offset; short day; /* for J or normal */ short week; short month; short rule_type; /* J, M, \0 */ char tzname[TZNAME_MAX+1];} rule_struct;#ifdef __UCLIBC_HAS_THREADS__#include <pthread.h>extern pthread_mutex_t _time_tzlock;#define TZLOCK __pthread_mutex_lock(&_time_tzlock)#define TZUNLOCK __pthread_mutex_unlock(&_time_tzlock)#else#define TZLOCK ((void) 0)#define TZUNLOCK ((void) 0)#endifextern rule_struct _time_tzinfo[2];extern struct tm *_time_t2tm(const time_t *__restrict timer, int offset, struct tm *__restrict result);extern time_t _time_mktime(struct tm *timeptr, int store_on_success);extern struct tm *__time_localtime_tzi(const time_t *__restrict timer, struct tm *__restrict result, rule_struct *tzi);extern time_t _time_mktime_tzi(struct tm *timeptr, int store_on_success, rule_struct *tzi);/**********************************************************************/#ifdef L_asctimestatic char __time_str[26];char *asctime(const struct tm *__restrict ptm){ return asctime_r(ptm, __time_str);}#endif/**********************************************************************/#ifdef L_asctime_r/* Strictly speaking, this implementation isn't correct. ANSI/ISO specifies * that the implementation of asctime() be equivalent to * * char *asctime(const struct tm *timeptr) * { * static char wday_name[7][3] = { * "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" * }; * static char mon_name[12][3] = { * "Jan", "Feb", "Mar", "Apr", "May", "Jun", * "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" * }; * static char result[26]; * * sprintf(result, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n", * wday_name[timeptr->tm_wday], * mon_name[timeptr->tm_mon], * timeptr->tm_mday, timeptr->tm_hour, * timeptr->tm_min, timeptr->tm_sec, * 1900 + timeptr->tm_year); * return result; * } * * but the above is either inherently unsafe, or carries with it the implicit * assumption that all fields of timeptr fall within their usual ranges, and * that the tm_year value falls in the range [-2899,8099] to avoid overflowing * the static buffer. * * If we take the implicit assumption as given, then the implementation below * is still incorrect for tm_year values < -900, as there will be either * 0-padding and/or a missing negative sign for the year conversion . But given * the ususal use of asctime(), I think it isn't unreasonable to restrict correct * operation to the domain of years between 1000 and 9999. *//* This is generally a good thing, but if you're _sure_ any data passed will be * in range, you can #undef this. */#define SAFE_ASCTIME_R 1static const unsigned char at_data[] = { 'S', 'u', 'n', 'M', 'o', 'n', 'T', 'u', 'e', 'W', 'e', 'd', 'T', 'h', 'u', 'F', 'r', 'i', 'S', 'a', 't', 'J', 'a', 'n', 'F', 'e', 'b', 'M', 'a', 'r', 'A', 'p', 'r', 'M', 'a', 'y', 'J', 'u', 'n', 'J', 'u', 'l', 'A', 'u', 'g', 'S', 'e', 'p', 'O', 'c', 't', 'N', 'o', 'v', 'D', 'e', 'c', #ifdef SAFE_ASCTIME_R '?', '?', '?', #endif ' ', '?', '?', '?', ' ', '0', offsetof(struct tm, tm_mday), ' ', '0', offsetof(struct tm, tm_hour), ':', '0', offsetof(struct tm, tm_min), ':', '0', offsetof(struct tm, tm_sec), ' ', '?', '?', '?', '?', '\n', 0};char *asctime_r(register const struct tm *__restrict ptm, register char *__restrict buffer){ int tmp; assert(ptm); assert(buffer);#ifdef SAFE_ASCTIME_R memcpy(buffer, at_data + 3*(7 + 12), sizeof(at_data) - 3*(7 + 12)); if (((unsigned int)(ptm->tm_wday)) <= 6) { memcpy(buffer, at_data + 3 * ptm->tm_wday, 3); } if (((unsigned int)(ptm->tm_mon)) <= 11) { memcpy(buffer + 4, at_data + 3*7 + 3 * ptm->tm_mon, 3); }#else assert(((unsigned int)(ptm->tm_wday)) <= 6); assert(((unsigned int)(ptm->tm_mon)) <= 11); memcpy(buffer, at_data + 3*(7 + 12) - 3, sizeof(at_data) + 3 - 3*(7 + 12)); memcpy(buffer, at_data + 3 * ptm->tm_wday, 3); memcpy(buffer + 4, at_data + 3*7 + 3 * ptm->tm_mon, 3);#endif#ifdef SAFE_ASCTIME_R buffer += 19; tmp = ptm->tm_year + 1900; if (((unsigned int) tmp) < 10000) { buffer += 4; do { *buffer = '0' + (tmp % 10); tmp /= 10; } while (*--buffer == '?'); }#else /* SAFE_ASCTIME_R */ buffer += 23; tmp = ptm->tm_year + 1900; assert( ((unsigned int) tmp) < 10000 ); do { *buffer = '0' + (tmp % 10); tmp /= 10; } while (*--buffer == '?');#endif /* SAFE_ASCTIME_R */ do { --buffer; tmp = *((int *)(((const char *) ptm) + (int) *buffer));#ifdef SAFE_ASCTIME_R if (((unsigned int) tmp) >= 100) { /* Just check 2 digit non-neg. */ buffer[-1] = *buffer = '?'; } else#else /* SAFE_ASCTIME_R */ assert(((unsigned int) tmp) < 100); /* Just check 2 digit non-neg. */#endif /* SAFE_ASCTIME_R */ { *buffer = '0' + (tmp % 10);#ifdef __BCC__ buffer[-1] = '0' + (tmp/10);#else /* __BCC__ */ buffer[-1] += (tmp/10);#endif /* __BCC__ */ } } while ((buffer -= 2)[-2] == '0'); if (*++buffer == '0') { /* Space-pad day of month. */ *buffer = ' '; } return buffer - 8;}#endif/**********************************************************************/#ifdef L_clock#include <sys/times.h>#ifndef __BCC__#if CLOCKS_PER_SEC != 1000000L#error unexpected value for CLOCKS_PER_SEC!#endif#endif#ifdef __UCLIBC_CLK_TCK_CONST# if __UCLIBC_CLK_TCK_CONST > CLOCKS_PER_SEC# error __UCLIBC_CLK_TCK_CONST > CLOCKS_PER_SEC!# elif __UCLIBC_CLK_TCK_CONST < 1# error __UCLIBC_CLK_TCK_CONST < 1!# endif#endif/* Note: SUSv3 notes * * On XSI-conformant systems, CLOCKS_PER_SEC is defined to be one million. * * The value returned by clock() may wrap around on some implementations. * For example, on a machine with 32-bit values for clock_t, it wraps * after 2147 seconds. * * This implies that we should bitwise and with LONG_MAX. */clock_t clock(void){ struct tms xtms; unsigned long t; times(&xtms); t = ((unsigned long) xtms.tms_utime) + xtms.tms_stime;#ifndef __UCLIBC_CLK_TCK_CONST# error __UCLIBC_CLK_TCK_CONST not defined!#elif ((CLOCKS_PER_SEC % __UCLIBC_CLK_TCK_CONST) == 0) /* CLOCKS_PER_SEC == k * __UCLIBC_CLK_TCK_CONST for some integer k >= 1. */ return ((t * (CLOCKS_PER_SEC/__UCLIBC_CLK_TCK_CONST)) & LONG_MAX);#else /* Unlike the previous case, the scaling factor is not an integer. * So when tms_utime, tms_stime, or their sum wraps, some of the * "visible" bits in the return value are affected. Nothing we * can really do about this though other than handle tms_utime and * tms_stime seperately and then sum. But since that doesn't really * buy us much, we don't bother. */ return ((((t / __UCLIBC_CLK_TCK_CONST) * CLOCKS_PER_SEC) + ((((t % __UCLIBC_CLK_TCK_CONST) * CLOCKS_PER_SEC) / __UCLIBC_CLK_TCK_CONST)) ) & LONG_MAX);#endif}#endif/**********************************************************************/#ifdef L_ctimechar *ctime(const time_t *clock){ /* ANSI/ISO/SUSv3 say that ctime is equivalent to the following. */ return asctime(localtime(clock));}#endif/**********************************************************************/#ifdef L_ctime_rchar *ctime_r(const time_t *clock, char *buf){ struct tm xtm; return asctime_r(localtime_r(clock, &xtm), buf);}#endif/**********************************************************************/#ifdef L_difftime#include <float.h>#if FLT_RADIX != 2#error difftime implementation assumptions violated for you arch!#endifdouble difftime(time_t time1, time_t time0){#if (LONG_MAX >> DBL_MANT_DIG) == 0 /* time_t fits in the mantissa of a double. */ return ((double) time1) - time0;#elif ((LONG_MAX >> DBL_MANT_DIG) >> DBL_MANT_DIG) == 0 /* time_t can overflow the mantissa of a double. */ time_t t1, t0, d; d = ((time_t) 1) << DBL_MANT_DIG; t1 = time1 / d; time1 -= (t1 * d); t0 = time0 / d; time0 -= (t0*d); /* Since FLT_RADIX==2 and d is a power of 2, the only possible * rounding error in the expression below would occur from the * addition. */ return (((double) t1) - t0) * d + (((double) time1) - time0);#else#error difftime needs special implementation on your arch.#endif}#endif/**********************************************************************/#ifdef L_gmtimestruct tm *gmtime(const time_t *timer){ register struct tm *ptm = &__time_tm; _time_t2tm(timer, 0, ptm); /* Can return NULL... */ return ptm;}#endif/**********************************************************************/#ifdef L_gmtime_rstruct tm *gmtime_r(const time_t *__restrict timer, struct tm *__restrict result){ return _time_t2tm(timer, 0, result);}#endif/**********************************************************************/#ifdef L_localtimestruct tm *localtime(const time_t *timer){ register struct tm *ptm = &__time_tm; /* In this implementation, tzset() is called by localtime_r(). */ localtime_r(timer, ptm); /* Can return NULL... */ return ptm;}#endif/**********************************************************************/#ifdef L_localtime_rstruct tm *localtime_r(register const time_t *__restrict timer, register struct tm *__restrict result){ TZLOCK; tzset(); __time_localtime_tzi(timer, result, _time_tzinfo); TZUNLOCK; return result;}#endif/**********************************************************************/#ifdef L__time_localtime_tzi#ifdef __UCLIBC_HAS_TM_EXTENSIONS__struct ll_tzname_item;typedef struct ll_tzname_item { struct ll_tzname_item *next; char tzname[TZNAME_MAX+1];} ll_tzname_item_t;static ll_tzname_item_t ll_tzname[] = { { ll_tzname + 1, "UTC" }, /* Always 1st. */ { NULL, "???" } /* Always 2nd. (invalid or out-of-memory) */};static const char *lookup_tzname(const char *key){ ll_tzname_item_t *p; for (p=ll_tzname ; p ; p=p->next) { if (!strcmp(p->tzname, key)) { return p->tzname; } } /* Hmm... a new name. */ if (strnlen(key, TZNAME_MAX+1) < TZNAME_MAX+1) { /* Verify legal length */ if ((p = malloc(sizeof(ll_tzname_item_t))) != NULL) { /* Insert as 3rd item in the list. */ p->next = ll_tzname[1].next; ll_tzname[1].next = p;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -