Rich Felker | 0b44a03 | 2011-02-12 00:22:29 -0500 | [diff] [blame] | 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
Rich Felker | 8b491f1 | 2013-08-23 08:11:43 -0400 | [diff] [blame] | 3 | #include <string.h> |
Rich Felker | 0b44a03 | 2011-02-12 00:22:29 -0500 | [diff] [blame] | 4 | #include <langinfo.h> |
Rich Felker | 0a37d99 | 2013-07-24 17:58:31 -0400 | [diff] [blame] | 5 | #include <locale.h> |
Rich Felker | 0b44a03 | 2011-02-12 00:22:29 -0500 | [diff] [blame] | 6 | #include <time.h> |
Rich Felker | 062446a | 2013-06-28 12:12:55 -0400 | [diff] [blame] | 7 | #include <limits.h> |
Rich Felker | 4c48501 | 2014-07-02 21:46:41 -0400 | [diff] [blame^] | 8 | #include "locale_impl.h" |
Rich Felker | 0a37d99 | 2013-07-24 17:58:31 -0400 | [diff] [blame] | 9 | #include "libc.h" |
Rich Felker | 242a4bb | 2013-08-25 02:02:15 -0400 | [diff] [blame] | 10 | #include "time_impl.h" |
Rich Felker | 0b44a03 | 2011-02-12 00:22:29 -0500 | [diff] [blame] | 11 | |
Rich Felker | 87be54a | 2013-07-24 18:52:02 -0400 | [diff] [blame] | 12 | const char *__nl_langinfo_l(nl_item, locale_t); |
Rich Felker | 0b44a03 | 2011-02-12 00:22:29 -0500 | [diff] [blame] | 13 | |
Rich Felker | c5faf1b | 2013-06-28 12:03:58 -0400 | [diff] [blame] | 14 | static int is_leap(int y) |
| 15 | { |
| 16 | /* Avoid overflow */ |
| 17 | if (y>INT_MAX-1900) y -= 2000; |
| 18 | y += 1900; |
| 19 | return !(y%4) && ((y%100) || !(y%400)); |
| 20 | } |
| 21 | |
Rich Felker | aea7919 | 2013-06-28 12:38:42 -0400 | [diff] [blame] | 22 | static int week_num(const struct tm *tm) |
| 23 | { |
| 24 | int val = (tm->tm_yday + 7 - (tm->tm_wday+6)%7) / 7; |
| 25 | /* If 1 Jan is just 1-3 days past Monday, |
| 26 | * the previous week is also in this year. */ |
| 27 | if ((tm->tm_wday - tm->tm_yday - 2 + 371) % 7 <= 2) |
| 28 | val++; |
| 29 | if (!val) { |
| 30 | val = 52; |
| 31 | /* If 31 December of prev year a Thursday, |
| 32 | * or Friday of a leap year, then the |
| 33 | * prev year has 53 weeks. */ |
| 34 | int dec31 = (tm->tm_wday - tm->tm_yday - 1 + 7) % 7; |
| 35 | if (dec31 == 4 || (dec31 == 5 && is_leap(tm->tm_year%400-1))) |
| 36 | val++; |
| 37 | } else if (val == 53) { |
| 38 | /* If 1 January is not a Thursday, and not |
| 39 | * a Wednesday of a leap year, then this |
| 40 | * year has only 52 weeks. */ |
| 41 | int jan1 = (tm->tm_wday - tm->tm_yday + 371) % 7; |
| 42 | if (jan1 != 4 && (jan1 != 3 || !is_leap(tm->tm_year))) |
| 43 | val = 1; |
| 44 | } |
| 45 | return val; |
| 46 | } |
| 47 | |
Rich Felker | d78be39 | 2013-08-24 12:59:02 -0400 | [diff] [blame] | 48 | const char *__tm_to_tzname(const struct tm *); |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 49 | size_t __strftime_l(char *restrict, size_t, const char *restrict, const struct tm *restrict, locale_t); |
| 50 | |
Rich Felker | 87e133b | 2013-08-22 19:36:30 -0400 | [diff] [blame] | 51 | const char *__strftime_fmt_1(char (*s)[100], size_t *l, int f, const struct tm *tm, locale_t loc) |
Rich Felker | 0b44a03 | 2011-02-12 00:22:29 -0500 | [diff] [blame] | 52 | { |
| 53 | nl_item item; |
Rich Felker | 33413cd | 2013-08-22 19:44:02 -0400 | [diff] [blame] | 54 | long long val; |
Rich Felker | 0b44a03 | 2011-02-12 00:22:29 -0500 | [diff] [blame] | 55 | const char *fmt; |
Rich Felker | 33413cd | 2013-08-22 19:44:02 -0400 | [diff] [blame] | 56 | int width = 2; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 57 | |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 58 | switch (f) { |
| 59 | case 'a': |
| 60 | item = ABDAY_1 + tm->tm_wday; |
| 61 | goto nl_strcat; |
| 62 | case 'A': |
| 63 | item = DAY_1 + tm->tm_wday; |
| 64 | goto nl_strcat; |
| 65 | case 'h': |
| 66 | case 'b': |
| 67 | item = ABMON_1 + tm->tm_mon; |
| 68 | goto nl_strcat; |
| 69 | case 'B': |
| 70 | item = MON_1 + tm->tm_mon; |
| 71 | goto nl_strcat; |
| 72 | case 'c': |
| 73 | item = D_T_FMT; |
| 74 | goto nl_strftime; |
| 75 | case 'C': |
Rich Felker | 33413cd | 2013-08-22 19:44:02 -0400 | [diff] [blame] | 76 | val = (1900LL+tm->tm_year) / 100; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 77 | goto number; |
| 78 | case 'd': |
| 79 | val = tm->tm_mday; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 80 | goto number; |
| 81 | case 'D': |
| 82 | fmt = "%m/%d/%y"; |
| 83 | goto recu_strftime; |
| 84 | case 'e': |
Rich Felker | 2828a13 | 2013-08-24 14:35:17 -0400 | [diff] [blame] | 85 | *l = snprintf(*s, sizeof *s, "%2d", tm->tm_mday); |
| 86 | return *s; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 87 | case 'F': |
| 88 | fmt = "%Y-%m-%d"; |
| 89 | goto recu_strftime; |
| 90 | case 'g': |
| 91 | case 'G': |
Rich Felker | 33413cd | 2013-08-22 19:44:02 -0400 | [diff] [blame] | 92 | val = tm->tm_year + 1900LL; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 93 | if (tm->tm_yday < 3 && week_num(tm) != 1) val--; |
| 94 | else if (tm->tm_yday > 360 && week_num(tm) == 1) val++; |
Rich Felker | 33413cd | 2013-08-22 19:44:02 -0400 | [diff] [blame] | 95 | if (f=='g') val %= 100; |
| 96 | else width = 4; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 97 | goto number; |
| 98 | case 'H': |
| 99 | val = tm->tm_hour; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 100 | goto number; |
| 101 | case 'I': |
| 102 | val = tm->tm_hour; |
| 103 | if (!val) val = 12; |
| 104 | else if (val > 12) val -= 12; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 105 | goto number; |
| 106 | case 'j': |
| 107 | val = tm->tm_yday+1; |
Rich Felker | 33413cd | 2013-08-22 19:44:02 -0400 | [diff] [blame] | 108 | width = 3; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 109 | goto number; |
| 110 | case 'm': |
| 111 | val = tm->tm_mon+1; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 112 | goto number; |
| 113 | case 'M': |
| 114 | val = tm->tm_min; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 115 | goto number; |
| 116 | case 'n': |
Rich Felker | 87e133b | 2013-08-22 19:36:30 -0400 | [diff] [blame] | 117 | *l = 1; |
Rich Felker | 45849d3 | 2013-08-22 19:27:36 -0400 | [diff] [blame] | 118 | return "\n"; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 119 | case 'p': |
| 120 | item = tm->tm_hour >= 12 ? PM_STR : AM_STR; |
| 121 | goto nl_strcat; |
| 122 | case 'r': |
| 123 | item = T_FMT_AMPM; |
| 124 | goto nl_strftime; |
| 125 | case 'R': |
| 126 | fmt = "%H:%M"; |
| 127 | goto recu_strftime; |
Rich Felker | 242a4bb | 2013-08-25 02:02:15 -0400 | [diff] [blame] | 128 | case 's': |
| 129 | val = __tm_to_secs(tm) + tm->__tm_gmtoff; |
Szabolcs Nagy | ac0acd5 | 2014-05-08 19:04:48 +0200 | [diff] [blame] | 130 | width = 1; |
Rich Felker | 242a4bb | 2013-08-25 02:02:15 -0400 | [diff] [blame] | 131 | goto number; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 132 | case 'S': |
| 133 | val = tm->tm_sec; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 134 | goto number; |
| 135 | case 't': |
Rich Felker | 87e133b | 2013-08-22 19:36:30 -0400 | [diff] [blame] | 136 | *l = 1; |
Rich Felker | 45849d3 | 2013-08-22 19:27:36 -0400 | [diff] [blame] | 137 | return "\t"; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 138 | case 'T': |
| 139 | fmt = "%H:%M:%S"; |
| 140 | goto recu_strftime; |
| 141 | case 'u': |
| 142 | val = tm->tm_wday ? tm->tm_wday : 7; |
Rich Felker | 33413cd | 2013-08-22 19:44:02 -0400 | [diff] [blame] | 143 | width = 1; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 144 | goto number; |
| 145 | case 'U': |
| 146 | val = (tm->tm_yday + 7 - tm->tm_wday) / 7; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 147 | goto number; |
| 148 | case 'W': |
| 149 | val = (tm->tm_yday + 7 - (tm->tm_wday+6)%7) / 7; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 150 | goto number; |
| 151 | case 'V': |
| 152 | val = week_num(tm); |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 153 | goto number; |
| 154 | case 'w': |
| 155 | val = tm->tm_wday; |
Rich Felker | 33413cd | 2013-08-22 19:44:02 -0400 | [diff] [blame] | 156 | width = 1; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 157 | goto number; |
| 158 | case 'x': |
| 159 | item = D_FMT; |
| 160 | goto nl_strftime; |
| 161 | case 'X': |
| 162 | item = T_FMT; |
| 163 | goto nl_strftime; |
| 164 | case 'y': |
| 165 | val = tm->tm_year % 100; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 166 | goto number; |
| 167 | case 'Y': |
Rich Felker | fc48cee | 2013-08-22 22:36:19 -0400 | [diff] [blame] | 168 | val = tm->tm_year + 1900; |
| 169 | if (val >= 10000) { |
| 170 | *l = snprintf(*s, sizeof *s, "+%lld", val); |
| 171 | return *s; |
| 172 | } |
Rich Felker | 33413cd | 2013-08-22 19:44:02 -0400 | [diff] [blame] | 173 | width = 4; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 174 | goto number; |
| 175 | case 'z': |
Rich Felker | d78be39 | 2013-08-24 12:59:02 -0400 | [diff] [blame] | 176 | if (tm->tm_isdst < 0) { |
| 177 | *l = 0; |
| 178 | return ""; |
| 179 | } |
| 180 | *l = snprintf(*s, sizeof *s, "%+.2d%.2d", |
| 181 | (-tm->__tm_gmtoff)/3600, |
| 182 | abs(tm->__tm_gmtoff%3600)/60); |
Rich Felker | 45849d3 | 2013-08-22 19:27:36 -0400 | [diff] [blame] | 183 | return *s; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 184 | case 'Z': |
Rich Felker | d78be39 | 2013-08-24 12:59:02 -0400 | [diff] [blame] | 185 | if (tm->tm_isdst < 0) { |
| 186 | *l = 0; |
| 187 | return ""; |
| 188 | } |
| 189 | fmt = __tm_to_tzname(tm); |
Rich Felker | 87e133b | 2013-08-22 19:36:30 -0400 | [diff] [blame] | 190 | goto string; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 191 | case '%': |
Rich Felker | 87e133b | 2013-08-22 19:36:30 -0400 | [diff] [blame] | 192 | *l = 1; |
Rich Felker | 45849d3 | 2013-08-22 19:27:36 -0400 | [diff] [blame] | 193 | return "%"; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 194 | default: |
| 195 | return 0; |
| 196 | } |
| 197 | number: |
Rich Felker | 33413cd | 2013-08-22 19:44:02 -0400 | [diff] [blame] | 198 | *l = snprintf(*s, sizeof *s, "%0*lld", width, val); |
Rich Felker | 45849d3 | 2013-08-22 19:27:36 -0400 | [diff] [blame] | 199 | return *s; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 200 | nl_strcat: |
Rich Felker | 87e133b | 2013-08-22 19:36:30 -0400 | [diff] [blame] | 201 | fmt = __nl_langinfo_l(item, loc); |
| 202 | string: |
| 203 | *l = strlen(fmt); |
| 204 | return fmt; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 205 | nl_strftime: |
| 206 | fmt = __nl_langinfo_l(item, loc); |
| 207 | recu_strftime: |
Rich Felker | 87e133b | 2013-08-22 19:36:30 -0400 | [diff] [blame] | 208 | *l = __strftime_l(*s, sizeof *s, fmt, tm, loc); |
| 209 | if (!*l) return 0; |
Rich Felker | 45849d3 | 2013-08-22 19:27:36 -0400 | [diff] [blame] | 210 | return *s; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 211 | } |
| 212 | |
| 213 | size_t __strftime_l(char *restrict s, size_t n, const char *restrict f, const struct tm *restrict tm, locale_t loc) |
| 214 | { |
| 215 | size_t l, k; |
Rich Felker | 45849d3 | 2013-08-22 19:27:36 -0400 | [diff] [blame] | 216 | char buf[100]; |
Rich Felker | fc48cee | 2013-08-22 22:36:19 -0400 | [diff] [blame] | 217 | char *p; |
Rich Felker | 45849d3 | 2013-08-22 19:27:36 -0400 | [diff] [blame] | 218 | const char *t; |
Rich Felker | fc48cee | 2013-08-22 22:36:19 -0400 | [diff] [blame] | 219 | int plus; |
| 220 | unsigned long width; |
Rich Felker | f63b8c8 | 2013-11-26 20:01:21 -0500 | [diff] [blame] | 221 | for (l=0; l<n; f++) { |
Rich Felker | 45849d3 | 2013-08-22 19:27:36 -0400 | [diff] [blame] | 222 | if (!*f) { |
| 223 | s[l] = 0; |
| 224 | return l; |
| 225 | } |
Rich Felker | d53b1f8 | 2013-07-27 17:47:03 -0400 | [diff] [blame] | 226 | if (*f != '%') { |
Rich Felker | d53b1f8 | 2013-07-27 17:47:03 -0400 | [diff] [blame] | 227 | s[l++] = *f; |
| 228 | continue; |
| 229 | } |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 230 | f++; |
Rich Felker | fc48cee | 2013-08-22 22:36:19 -0400 | [diff] [blame] | 231 | if ((plus = (*f == '+'))) f++; |
| 232 | width = strtoul(f, &p, 10); |
| 233 | if (*p == 'C' || *p == 'F' || *p == 'G' || *p == 'Y') { |
| 234 | if (!width && p!=f) width = 1; |
Rich Felker | fc48cee | 2013-08-22 22:36:19 -0400 | [diff] [blame] | 235 | } else { |
| 236 | width = 0; |
| 237 | } |
| 238 | f = p; |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 239 | if (*f == 'E' || *f == 'O') f++; |
Rich Felker | 87e133b | 2013-08-22 19:36:30 -0400 | [diff] [blame] | 240 | t = __strftime_fmt_1(&buf, &k, *f, tm, loc); |
Rich Felker | f63b8c8 | 2013-11-26 20:01:21 -0500 | [diff] [blame] | 241 | if (!t) break; |
Rich Felker | fc48cee | 2013-08-22 22:36:19 -0400 | [diff] [blame] | 242 | if (width) { |
| 243 | for (; *t=='+' || *t=='-' || (*t=='0'&&t[1]); t++, k--); |
| 244 | width--; |
| 245 | if (plus && tm->tm_year >= 10000-1900) |
| 246 | s[l++] = '+'; |
| 247 | else if (tm->tm_year < -1900) |
| 248 | s[l++] = '-'; |
| 249 | else |
| 250 | width++; |
Rich Felker | f63b8c8 | 2013-11-26 20:01:21 -0500 | [diff] [blame] | 251 | for (; width > k && l < n; width--) |
Rich Felker | fc48cee | 2013-08-22 22:36:19 -0400 | [diff] [blame] | 252 | s[l++] = '0'; |
| 253 | } |
Rich Felker | f63b8c8 | 2013-11-26 20:01:21 -0500 | [diff] [blame] | 254 | if (k > n-l) k = n-l; |
Rich Felker | 45849d3 | 2013-08-22 19:27:36 -0400 | [diff] [blame] | 255 | memcpy(s+l, t, k); |
Rich Felker | f5e4efc | 2013-08-22 19:02:52 -0400 | [diff] [blame] | 256 | l += k; |
Rich Felker | 0b44a03 | 2011-02-12 00:22:29 -0500 | [diff] [blame] | 257 | } |
Rich Felker | f63b8c8 | 2013-11-26 20:01:21 -0500 | [diff] [blame] | 258 | if (n) { |
| 259 | if (l==n) l=n-1; |
| 260 | s[l] = 0; |
| 261 | } |
Rich Felker | 45849d3 | 2013-08-22 19:27:36 -0400 | [diff] [blame] | 262 | return 0; |
Rich Felker | 0b44a03 | 2011-02-12 00:22:29 -0500 | [diff] [blame] | 263 | } |
Rich Felker | 0a37d99 | 2013-07-24 17:58:31 -0400 | [diff] [blame] | 264 | |
| 265 | size_t strftime(char *restrict s, size_t n, const char *restrict f, const struct tm *restrict tm) |
| 266 | { |
Rich Felker | 4c48501 | 2014-07-02 21:46:41 -0400 | [diff] [blame^] | 267 | return __strftime_l(s, n, f, tm, CURRENT_LOCALE); |
Rich Felker | 0a37d99 | 2013-07-24 17:58:31 -0400 | [diff] [blame] | 268 | } |
| 269 | |
| 270 | weak_alias(__strftime_l, strftime_l); |