2011-02-12 00:22:29 -05:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
2013-08-23 08:11:43 -04:00
|
|
|
#include <string.h>
|
2011-02-12 00:22:29 -05:00
|
|
|
#include <langinfo.h>
|
2013-07-24 17:58:31 -04:00
|
|
|
#include <locale.h>
|
2011-02-12 00:22:29 -05:00
|
|
|
#include <time.h>
|
2013-06-28 12:12:55 -04:00
|
|
|
#include <limits.h>
|
2014-07-02 21:46:41 -04:00
|
|
|
#include "locale_impl.h"
|
2013-07-24 17:58:31 -04:00
|
|
|
#include "libc.h"
|
2013-08-25 02:02:15 -04:00
|
|
|
#include "time_impl.h"
|
2011-02-12 00:22:29 -05:00
|
|
|
|
2013-07-24 18:52:02 -04:00
|
|
|
const char *__nl_langinfo_l(nl_item, locale_t);
|
2011-02-12 00:22:29 -05:00
|
|
|
|
2013-06-28 12:03:58 -04:00
|
|
|
static int is_leap(int y)
|
|
|
|
{
|
|
|
|
/* Avoid overflow */
|
|
|
|
if (y>INT_MAX-1900) y -= 2000;
|
|
|
|
y += 1900;
|
|
|
|
return !(y%4) && ((y%100) || !(y%400));
|
|
|
|
}
|
|
|
|
|
2013-06-28 12:38:42 -04:00
|
|
|
static int week_num(const struct tm *tm)
|
|
|
|
{
|
2015-10-14 18:58:15 -04:00
|
|
|
int val = (tm->tm_yday + 7U - (tm->tm_wday+6U)%7) / 7;
|
2013-06-28 12:38:42 -04:00
|
|
|
/* If 1 Jan is just 1-3 days past Monday,
|
|
|
|
* the previous week is also in this year. */
|
2015-10-14 18:58:15 -04:00
|
|
|
if ((tm->tm_wday + 371U - tm->tm_yday - 2) % 7 <= 2)
|
2013-06-28 12:38:42 -04:00
|
|
|
val++;
|
|
|
|
if (!val) {
|
|
|
|
val = 52;
|
|
|
|
/* If 31 December of prev year a Thursday,
|
|
|
|
* or Friday of a leap year, then the
|
|
|
|
* prev year has 53 weeks. */
|
2015-10-14 18:58:15 -04:00
|
|
|
int dec31 = (tm->tm_wday + 7U - tm->tm_yday - 1) % 7;
|
2013-06-28 12:38:42 -04:00
|
|
|
if (dec31 == 4 || (dec31 == 5 && is_leap(tm->tm_year%400-1)))
|
|
|
|
val++;
|
|
|
|
} else if (val == 53) {
|
|
|
|
/* If 1 January is not a Thursday, and not
|
|
|
|
* a Wednesday of a leap year, then this
|
|
|
|
* year has only 52 weeks. */
|
2015-10-14 18:58:15 -04:00
|
|
|
int jan1 = (tm->tm_wday + 371U - tm->tm_yday) % 7;
|
2013-06-28 12:38:42 -04:00
|
|
|
if (jan1 != 4 && (jan1 != 3 || !is_leap(tm->tm_year)))
|
|
|
|
val = 1;
|
|
|
|
}
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
fix strftime handling of time zone data
this may need further revision in the future, since POSIX is rather
unclear on the requirements, and is designed around the assumption of
POSIX TZ specifiers which are not sufficiently powerful to represent
real-world timezones (this is why zoneinfo support was added).
the basic issue is that strftime gets the string and numeric offset
for the timezone from the extra fields in struct tm, which are
initialized when calling localtime/gmtime/etc. however, a conforming
application might have created its own struct tm without initializing
these fields, in which case using __tm_zone (a pointer) could crash.
other zoneinfo-based implementations simply check for a null pointer,
but otherwise can still crash of the field contains junk.
simply ignoring __tm_zone and using tzname[] would "work" but would
give incorrect results in time zones with more complex rules. I feel
like this would lower the quality of implementation.
instead, simply validate __tm_zone: unless it points to one of the
zone name strings managed by the timezone system, assume it's invalid.
this commit also fixes several other minor bugs with formatting:
tm_isdst being negative is required to suppress printing of the zone
formats, and %z was using the wrong format specifiers since the type
of val was changed, resulting in bogus output.
2013-08-24 12:59:02 -04:00
|
|
|
const char *__tm_to_tzname(const struct tm *);
|
2013-08-22 19:02:52 -04:00
|
|
|
size_t __strftime_l(char *restrict, size_t, const char *restrict, const struct tm *restrict, locale_t);
|
|
|
|
|
2013-08-22 19:36:30 -04:00
|
|
|
const char *__strftime_fmt_1(char (*s)[100], size_t *l, int f, const struct tm *tm, locale_t loc)
|
2011-02-12 00:22:29 -05:00
|
|
|
{
|
|
|
|
nl_item item;
|
2013-08-22 19:44:02 -04:00
|
|
|
long long val;
|
2015-10-14 18:58:15 -04:00
|
|
|
const char *fmt = "-";
|
2013-08-22 19:44:02 -04:00
|
|
|
int width = 2;
|
2013-08-22 19:02:52 -04:00
|
|
|
|
|
|
|
switch (f) {
|
|
|
|
case 'a':
|
2015-10-14 18:58:15 -04:00
|
|
|
if (tm->tm_wday > 6U) goto string;
|
2013-08-22 19:02:52 -04:00
|
|
|
item = ABDAY_1 + tm->tm_wday;
|
|
|
|
goto nl_strcat;
|
|
|
|
case 'A':
|
2015-10-14 18:58:15 -04:00
|
|
|
if (tm->tm_wday > 6U) goto string;
|
2013-08-22 19:02:52 -04:00
|
|
|
item = DAY_1 + tm->tm_wday;
|
|
|
|
goto nl_strcat;
|
|
|
|
case 'h':
|
|
|
|
case 'b':
|
2015-10-14 18:58:15 -04:00
|
|
|
if (tm->tm_mon > 11U) goto string;
|
2013-08-22 19:02:52 -04:00
|
|
|
item = ABMON_1 + tm->tm_mon;
|
|
|
|
goto nl_strcat;
|
|
|
|
case 'B':
|
2015-10-14 18:58:15 -04:00
|
|
|
if (tm->tm_mon > 11U) goto string;
|
2013-08-22 19:02:52 -04:00
|
|
|
item = MON_1 + tm->tm_mon;
|
|
|
|
goto nl_strcat;
|
|
|
|
case 'c':
|
|
|
|
item = D_T_FMT;
|
|
|
|
goto nl_strftime;
|
|
|
|
case 'C':
|
2013-08-22 19:44:02 -04:00
|
|
|
val = (1900LL+tm->tm_year) / 100;
|
2013-08-22 19:02:52 -04:00
|
|
|
goto number;
|
|
|
|
case 'd':
|
|
|
|
val = tm->tm_mday;
|
|
|
|
goto number;
|
|
|
|
case 'D':
|
|
|
|
fmt = "%m/%d/%y";
|
|
|
|
goto recu_strftime;
|
|
|
|
case 'e':
|
2013-08-24 14:35:17 -04:00
|
|
|
*l = snprintf(*s, sizeof *s, "%2d", tm->tm_mday);
|
|
|
|
return *s;
|
2013-08-22 19:02:52 -04:00
|
|
|
case 'F':
|
|
|
|
fmt = "%Y-%m-%d";
|
|
|
|
goto recu_strftime;
|
|
|
|
case 'g':
|
|
|
|
case 'G':
|
2013-08-22 19:44:02 -04:00
|
|
|
val = tm->tm_year + 1900LL;
|
2013-08-22 19:02:52 -04:00
|
|
|
if (tm->tm_yday < 3 && week_num(tm) != 1) val--;
|
|
|
|
else if (tm->tm_yday > 360 && week_num(tm) == 1) val++;
|
2013-08-22 19:44:02 -04:00
|
|
|
if (f=='g') val %= 100;
|
|
|
|
else width = 4;
|
2013-08-22 19:02:52 -04:00
|
|
|
goto number;
|
|
|
|
case 'H':
|
|
|
|
val = tm->tm_hour;
|
|
|
|
goto number;
|
|
|
|
case 'I':
|
|
|
|
val = tm->tm_hour;
|
|
|
|
if (!val) val = 12;
|
|
|
|
else if (val > 12) val -= 12;
|
|
|
|
goto number;
|
|
|
|
case 'j':
|
|
|
|
val = tm->tm_yday+1;
|
2013-08-22 19:44:02 -04:00
|
|
|
width = 3;
|
2013-08-22 19:02:52 -04:00
|
|
|
goto number;
|
|
|
|
case 'm':
|
|
|
|
val = tm->tm_mon+1;
|
|
|
|
goto number;
|
|
|
|
case 'M':
|
|
|
|
val = tm->tm_min;
|
|
|
|
goto number;
|
|
|
|
case 'n':
|
2013-08-22 19:36:30 -04:00
|
|
|
*l = 1;
|
2013-08-22 19:27:36 -04:00
|
|
|
return "\n";
|
2013-08-22 19:02:52 -04:00
|
|
|
case 'p':
|
|
|
|
item = tm->tm_hour >= 12 ? PM_STR : AM_STR;
|
|
|
|
goto nl_strcat;
|
|
|
|
case 'r':
|
|
|
|
item = T_FMT_AMPM;
|
|
|
|
goto nl_strftime;
|
|
|
|
case 'R':
|
|
|
|
fmt = "%H:%M";
|
|
|
|
goto recu_strftime;
|
2013-08-25 02:02:15 -04:00
|
|
|
case 's':
|
2015-08-13 17:28:39 +02:00
|
|
|
val = __tm_to_secs(tm) - tm->__tm_gmtoff;
|
2014-05-08 19:04:48 +02:00
|
|
|
width = 1;
|
2013-08-25 02:02:15 -04:00
|
|
|
goto number;
|
2013-08-22 19:02:52 -04:00
|
|
|
case 'S':
|
|
|
|
val = tm->tm_sec;
|
|
|
|
goto number;
|
|
|
|
case 't':
|
2013-08-22 19:36:30 -04:00
|
|
|
*l = 1;
|
2013-08-22 19:27:36 -04:00
|
|
|
return "\t";
|
2013-08-22 19:02:52 -04:00
|
|
|
case 'T':
|
|
|
|
fmt = "%H:%M:%S";
|
|
|
|
goto recu_strftime;
|
|
|
|
case 'u':
|
|
|
|
val = tm->tm_wday ? tm->tm_wday : 7;
|
2013-08-22 19:44:02 -04:00
|
|
|
width = 1;
|
2013-08-22 19:02:52 -04:00
|
|
|
goto number;
|
|
|
|
case 'U':
|
2015-10-14 18:58:15 -04:00
|
|
|
val = (tm->tm_yday + 7U - tm->tm_wday) / 7;
|
2013-08-22 19:02:52 -04:00
|
|
|
goto number;
|
|
|
|
case 'W':
|
2015-10-14 18:58:15 -04:00
|
|
|
val = (tm->tm_yday + 7U - (tm->tm_wday+6U)%7) / 7;
|
2013-08-22 19:02:52 -04:00
|
|
|
goto number;
|
|
|
|
case 'V':
|
|
|
|
val = week_num(tm);
|
|
|
|
goto number;
|
|
|
|
case 'w':
|
|
|
|
val = tm->tm_wday;
|
2013-08-22 19:44:02 -04:00
|
|
|
width = 1;
|
2013-08-22 19:02:52 -04:00
|
|
|
goto number;
|
|
|
|
case 'x':
|
|
|
|
item = D_FMT;
|
|
|
|
goto nl_strftime;
|
|
|
|
case 'X':
|
|
|
|
item = T_FMT;
|
|
|
|
goto nl_strftime;
|
|
|
|
case 'y':
|
2017-01-02 17:30:40 -05:00
|
|
|
val = (tm->tm_year + 1900LL) % 100;
|
|
|
|
if (val < 0) val = -val;
|
2013-08-22 19:02:52 -04:00
|
|
|
goto number;
|
|
|
|
case 'Y':
|
2015-10-14 18:58:15 -04:00
|
|
|
val = tm->tm_year + 1900LL;
|
2013-08-22 22:36:19 -04:00
|
|
|
if (val >= 10000) {
|
|
|
|
*l = snprintf(*s, sizeof *s, "+%lld", val);
|
|
|
|
return *s;
|
|
|
|
}
|
2013-08-22 19:44:02 -04:00
|
|
|
width = 4;
|
2013-08-22 19:02:52 -04:00
|
|
|
goto number;
|
|
|
|
case 'z':
|
fix strftime handling of time zone data
this may need further revision in the future, since POSIX is rather
unclear on the requirements, and is designed around the assumption of
POSIX TZ specifiers which are not sufficiently powerful to represent
real-world timezones (this is why zoneinfo support was added).
the basic issue is that strftime gets the string and numeric offset
for the timezone from the extra fields in struct tm, which are
initialized when calling localtime/gmtime/etc. however, a conforming
application might have created its own struct tm without initializing
these fields, in which case using __tm_zone (a pointer) could crash.
other zoneinfo-based implementations simply check for a null pointer,
but otherwise can still crash of the field contains junk.
simply ignoring __tm_zone and using tzname[] would "work" but would
give incorrect results in time zones with more complex rules. I feel
like this would lower the quality of implementation.
instead, simply validate __tm_zone: unless it points to one of the
zone name strings managed by the timezone system, assume it's invalid.
this commit also fixes several other minor bugs with formatting:
tm_isdst being negative is required to suppress printing of the zone
formats, and %z was using the wrong format specifiers since the type
of val was changed, resulting in bogus output.
2013-08-24 12:59:02 -04:00
|
|
|
if (tm->tm_isdst < 0) {
|
|
|
|
*l = 0;
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
*l = snprintf(*s, sizeof *s, "%+.2d%.2d",
|
2015-08-13 17:28:39 +02:00
|
|
|
(tm->__tm_gmtoff)/3600,
|
fix strftime handling of time zone data
this may need further revision in the future, since POSIX is rather
unclear on the requirements, and is designed around the assumption of
POSIX TZ specifiers which are not sufficiently powerful to represent
real-world timezones (this is why zoneinfo support was added).
the basic issue is that strftime gets the string and numeric offset
for the timezone from the extra fields in struct tm, which are
initialized when calling localtime/gmtime/etc. however, a conforming
application might have created its own struct tm without initializing
these fields, in which case using __tm_zone (a pointer) could crash.
other zoneinfo-based implementations simply check for a null pointer,
but otherwise can still crash of the field contains junk.
simply ignoring __tm_zone and using tzname[] would "work" but would
give incorrect results in time zones with more complex rules. I feel
like this would lower the quality of implementation.
instead, simply validate __tm_zone: unless it points to one of the
zone name strings managed by the timezone system, assume it's invalid.
this commit also fixes several other minor bugs with formatting:
tm_isdst being negative is required to suppress printing of the zone
formats, and %z was using the wrong format specifiers since the type
of val was changed, resulting in bogus output.
2013-08-24 12:59:02 -04:00
|
|
|
abs(tm->__tm_gmtoff%3600)/60);
|
2013-08-22 19:27:36 -04:00
|
|
|
return *s;
|
2013-08-22 19:02:52 -04:00
|
|
|
case 'Z':
|
fix strftime handling of time zone data
this may need further revision in the future, since POSIX is rather
unclear on the requirements, and is designed around the assumption of
POSIX TZ specifiers which are not sufficiently powerful to represent
real-world timezones (this is why zoneinfo support was added).
the basic issue is that strftime gets the string and numeric offset
for the timezone from the extra fields in struct tm, which are
initialized when calling localtime/gmtime/etc. however, a conforming
application might have created its own struct tm without initializing
these fields, in which case using __tm_zone (a pointer) could crash.
other zoneinfo-based implementations simply check for a null pointer,
but otherwise can still crash of the field contains junk.
simply ignoring __tm_zone and using tzname[] would "work" but would
give incorrect results in time zones with more complex rules. I feel
like this would lower the quality of implementation.
instead, simply validate __tm_zone: unless it points to one of the
zone name strings managed by the timezone system, assume it's invalid.
this commit also fixes several other minor bugs with formatting:
tm_isdst being negative is required to suppress printing of the zone
formats, and %z was using the wrong format specifiers since the type
of val was changed, resulting in bogus output.
2013-08-24 12:59:02 -04:00
|
|
|
if (tm->tm_isdst < 0) {
|
|
|
|
*l = 0;
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
fmt = __tm_to_tzname(tm);
|
2013-08-22 19:36:30 -04:00
|
|
|
goto string;
|
2013-08-22 19:02:52 -04:00
|
|
|
case '%':
|
2013-08-22 19:36:30 -04:00
|
|
|
*l = 1;
|
2013-08-22 19:27:36 -04:00
|
|
|
return "%";
|
2013-08-22 19:02:52 -04:00
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
2011-02-12 00:22:29 -05:00
|
|
|
number:
|
2013-08-22 19:44:02 -04:00
|
|
|
*l = snprintf(*s, sizeof *s, "%0*lld", width, val);
|
2013-08-22 19:27:36 -04:00
|
|
|
return *s;
|
2011-02-12 00:22:29 -05:00
|
|
|
nl_strcat:
|
2013-08-22 19:36:30 -04:00
|
|
|
fmt = __nl_langinfo_l(item, loc);
|
|
|
|
string:
|
|
|
|
*l = strlen(fmt);
|
|
|
|
return fmt;
|
2011-02-12 00:22:29 -05:00
|
|
|
nl_strftime:
|
2013-08-22 19:02:52 -04:00
|
|
|
fmt = __nl_langinfo_l(item, loc);
|
2011-02-12 00:22:29 -05:00
|
|
|
recu_strftime:
|
2013-08-22 19:36:30 -04:00
|
|
|
*l = __strftime_l(*s, sizeof *s, fmt, tm, loc);
|
|
|
|
if (!*l) return 0;
|
2013-08-22 19:27:36 -04:00
|
|
|
return *s;
|
2013-08-22 19:02:52 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
size_t __strftime_l(char *restrict s, size_t n, const char *restrict f, const struct tm *restrict tm, locale_t loc)
|
|
|
|
{
|
|
|
|
size_t l, k;
|
2013-08-22 19:27:36 -04:00
|
|
|
char buf[100];
|
2013-08-22 22:36:19 -04:00
|
|
|
char *p;
|
2013-08-22 19:27:36 -04:00
|
|
|
const char *t;
|
2013-08-22 22:36:19 -04:00
|
|
|
int plus;
|
|
|
|
unsigned long width;
|
2013-11-26 20:01:21 -05:00
|
|
|
for (l=0; l<n; f++) {
|
2013-08-22 19:27:36 -04:00
|
|
|
if (!*f) {
|
|
|
|
s[l] = 0;
|
|
|
|
return l;
|
|
|
|
}
|
2013-08-22 19:02:52 -04:00
|
|
|
if (*f != '%') {
|
|
|
|
s[l++] = *f;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
f++;
|
2013-08-22 22:36:19 -04:00
|
|
|
if ((plus = (*f == '+'))) f++;
|
|
|
|
width = strtoul(f, &p, 10);
|
|
|
|
if (*p == 'C' || *p == 'F' || *p == 'G' || *p == 'Y') {
|
|
|
|
if (!width && p!=f) width = 1;
|
|
|
|
} else {
|
|
|
|
width = 0;
|
|
|
|
}
|
|
|
|
f = p;
|
2013-08-22 19:02:52 -04:00
|
|
|
if (*f == 'E' || *f == 'O') f++;
|
2013-08-22 19:36:30 -04:00
|
|
|
t = __strftime_fmt_1(&buf, &k, *f, tm, loc);
|
2013-11-26 20:01:21 -05:00
|
|
|
if (!t) break;
|
2013-08-22 22:36:19 -04:00
|
|
|
if (width) {
|
|
|
|
for (; *t=='+' || *t=='-' || (*t=='0'&&t[1]); t++, k--);
|
|
|
|
width--;
|
|
|
|
if (plus && tm->tm_year >= 10000-1900)
|
|
|
|
s[l++] = '+';
|
|
|
|
else if (tm->tm_year < -1900)
|
|
|
|
s[l++] = '-';
|
|
|
|
else
|
|
|
|
width++;
|
2013-11-26 20:01:21 -05:00
|
|
|
for (; width > k && l < n; width--)
|
2013-08-22 22:36:19 -04:00
|
|
|
s[l++] = '0';
|
|
|
|
}
|
2013-11-26 20:01:21 -05:00
|
|
|
if (k > n-l) k = n-l;
|
2013-08-22 19:27:36 -04:00
|
|
|
memcpy(s+l, t, k);
|
2013-08-22 19:02:52 -04:00
|
|
|
l += k;
|
2011-02-12 00:22:29 -05:00
|
|
|
}
|
2013-11-26 20:01:21 -05:00
|
|
|
if (n) {
|
|
|
|
if (l==n) l=n-1;
|
|
|
|
s[l] = 0;
|
|
|
|
}
|
2013-08-22 19:27:36 -04:00
|
|
|
return 0;
|
2011-02-12 00:22:29 -05:00
|
|
|
}
|
2013-07-24 17:58:31 -04:00
|
|
|
|
|
|
|
size_t strftime(char *restrict s, size_t n, const char *restrict f, const struct tm *restrict tm)
|
|
|
|
{
|
2014-07-02 21:46:41 -04:00
|
|
|
return __strftime_l(s, n, f, tm, CURRENT_LOCALE);
|
2013-07-24 17:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
weak_alias(__strftime_l, strftime_l);
|