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