cosmopolitan/libc/time/strftime.c

395 lines
13 KiB
C
Raw Normal View History

2020-06-15 14:18:57 +00:00
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright (c) 1989 The Regents of the University of California.
All rights reserved.
Redistribution and use in source and binary forms are permitted
provided that the above copyright notice and this paragraph are
duplicated in all such forms and that any documentation,
advertising materials, and other materials related to such
distribution and use acknowledge that the software was developed
by the University of California, Berkeley. The name of the
University may not be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include "libc/assert.h"
2020-06-15 14:18:57 +00:00
#include "libc/calls/calls.h"
#include "libc/fmt/fmt.h"
#include "libc/macros.h"
#include "libc/nexgen32e/nexgen32e.h"
2020-06-15 14:18:57 +00:00
#include "libc/time/struct/tm.h"
#include "libc/time/time.h"
#include "libc/time/tzfile.internal.h"
2020-06-15 14:18:57 +00:00
STATIC_YOINK("ntoa");
asm(".ident\t\"\\n\\n\
strftime (BSD-3)\\n\
Copyright 1989 The Regents of the University of California\"");
asm(".include \"libc/disclaimer.inc\"");
static char *strftime_add(char *p, const char *pe, const char *str) {
while (p < pe && (*p = *str++) != '\0') ++p;
return p;
2020-06-15 14:18:57 +00:00
}
static char *strftime_conv(char *p, const char *pe, int n, const char *format) {
2020-06-15 14:18:57 +00:00
char buf[INT_STRLEN_MAXIMUM(int) + 1];
(snprintf)(buf, sizeof(buf), format, n);
return strftime_add(p, pe, buf);
2020-06-15 14:18:57 +00:00
}
static char *strftime_secs(char *p, const char *pe, const struct tm *t) {
2020-06-15 14:18:57 +00:00
static char buf[INT_STRLEN_MAXIMUM(int) + 1];
struct tm tmp;
int64_t s;
tmp = *t; /* Make a copy, mktime(3) modifies the tm struct. */
s = mktime(&tmp);
(snprintf)(buf, sizeof(buf), "%ld", s);
return strftime_add(p, pe, buf);
2020-06-15 14:18:57 +00:00
}
static char *strftime_timefmt(char *p, const char *pe, const char *format,
2020-06-15 14:18:57 +00:00
const struct tm *t) {
int i;
long diff;
char const *sign;
/* size_t z1, z2, z3; */
for (; *format; ++format) {
if (*format == '%') {
label:
switch (*++format) {
case '\0':
--format;
break;
case 'A':
p = strftime_add(p, pe,
(t->tm_wday < 0 || t->tm_wday > 6)
? "?"
: kWeekdayName[t->tm_wday]);
2020-06-15 14:18:57 +00:00
continue;
case 'a':
p = strftime_add(p, pe,
(t->tm_wday < 0 || t->tm_wday > 6)
? "?"
: kWeekdayNameShort[t->tm_wday]);
2020-06-15 14:18:57 +00:00
continue;
case 'B':
p = strftime_add(
p, pe,
2020-06-15 14:18:57 +00:00
(t->tm_mon < 0 || t->tm_mon > 11) ? "?" : kMonthName[t->tm_mon]);
continue;
case 'b':
case 'h':
p = strftime_add(p, pe,
(t->tm_mon < 0 || t->tm_mon > 11)
? "?"
: kMonthNameShort[t->tm_mon]);
2020-06-15 14:18:57 +00:00
continue;
case 'c':
p = strftime_timefmt(p, pe, "%D %X", t);
2020-06-15 14:18:57 +00:00
continue;
case 'C':
/*
** %C used to do a...
** strftime_timefmt("%a %b %e %X %Y", t);
** ...whereas now POSIX 1003.2 calls for
** something completely different.
** (ado, 5/24/93)
*/
p = strftime_conv(p, pe, div100int64(t->tm_year + TM_YEAR_BASE),
"%02d");
2020-06-15 14:18:57 +00:00
continue;
case 'D':
p = strftime_timefmt(p, pe, "%m/%d/%y", t);
2020-06-15 14:18:57 +00:00
continue;
case 'x':
/*
** Version 3.0 of strftime from Arnold Robbins
** (arnold@skeeve.atl.ga.us) does the
** equivalent of...
** strftime_timefmt("%a %b %e %Y");
** ...for %x; since the X3J11 C language
** standard calls for "date, using locale's
** date format," anything goes. Using just
** numbers (as here) makes Quakers happier.
** Word from Paul Eggert (eggert@twinsun.com)
** is that %Y-%m-%d is the ISO standard date
** format, specified in ISO 2014 and later
** ISO 8601:1988, with a summary available in
** pub/doc/ISO/english/ISO8601.ps.Z on
** ftp.uni-erlangen.de.
** (ado, 5/30/93)
*/
p = strftime_timefmt(p, pe, "%m/%d/%y", t);
2020-06-15 14:18:57 +00:00
continue;
case 'd':
p = strftime_conv(p, pe, t->tm_mday, "%02d");
2020-06-15 14:18:57 +00:00
continue;
case 'E':
case 'O':
/*
** POSIX locale extensions, a la
** Arnold Robbins' strftime version 3.0.
** The sequences
** %Ec %EC %Ex %Ey %EY
** %Od %oe %OH %OI %Om %OM
** %OS %Ou %OU %OV %Ow %OW %Oy
** are supposed to provide alternate
** representations.
** (ado, 5/24/93)
*/
goto label;
case 'e':
p = strftime_conv(p, pe, t->tm_mday, "%2d");
2020-06-15 14:18:57 +00:00
continue;
case 'H':
p = strftime_conv(p, pe, t->tm_hour, "%02d");
2020-06-15 14:18:57 +00:00
continue;
case 'I':
p = strftime_conv(p, pe, (t->tm_hour % 12) ? (t->tm_hour % 12) : 12,
"%02d");
2020-06-15 14:18:57 +00:00
continue;
case 'j':
p = strftime_conv(p, pe, t->tm_yday + 1, "%03d");
2020-06-15 14:18:57 +00:00
continue;
case 'k':
/*
** This used to be...
** strftime_conv(t->tm_hour % 12 ?
** t->tm_hour % 12 : 12, 2, ' ');
** ...and has been changed to the below to
** match SunOS 4.1.1 and Arnold Robbins'
** strftime version 3.0. That is, "%k" and
** "%l" have been swapped.
** (ado, 5/24/93)
*/
p = strftime_conv(p, pe, t->tm_hour, "%2d");
2020-06-15 14:18:57 +00:00
continue;
#ifdef KITCHEN_SINK
case 'K':
/*
** After all this time, still unclaimed!
*/
p = strftime_add(p, pe, "kitchen sink");
2020-06-15 14:18:57 +00:00
continue;
#endif /* defined KITCHEN_SINK */
case 'l':
/*
** This used to be...
** strftime_conv(t->tm_hour, 2, ' ');
** ...and has been changed to the below to
** match SunOS 4.1.1 and Arnold Robbin's
** strftime version 3.0. That is, "%k" and
** "%l" have been swapped.
** (ado, 5/24/93)
*/
p = strftime_conv(p, pe, (t->tm_hour % 12) ? (t->tm_hour % 12) : 12,
"%2d");
2020-06-15 14:18:57 +00:00
continue;
case 'M':
p = strftime_conv(p, pe, t->tm_min, "%02d");
2020-06-15 14:18:57 +00:00
continue;
case 'm':
p = strftime_conv(p, pe, t->tm_mon + 1, "%02d");
2020-06-15 14:18:57 +00:00
continue;
case 'n':
p = strftime_add(p, pe, "\n");
2020-06-15 14:18:57 +00:00
continue;
case 'p':
p = strftime_add(p, pe, t->tm_hour >= 12 ? "PM" : "AM");
2020-06-15 14:18:57 +00:00
continue;
case 'R':
p = strftime_timefmt(p, pe, "%H:%M", t);
2020-06-15 14:18:57 +00:00
continue;
case 'r':
p = strftime_timefmt(p, pe, "%I:%M:%S %p", t);
2020-06-15 14:18:57 +00:00
continue;
case 'S':
p = strftime_conv(p, pe, t->tm_sec, "%02d");
2020-06-15 14:18:57 +00:00
continue;
case 's':
p = strftime_secs(p, pe, t);
2020-06-15 14:18:57 +00:00
continue;
case 'T':
case 'X':
p = strftime_timefmt(p, pe, "%H:%M:%S", t);
2020-06-15 14:18:57 +00:00
continue;
case 't':
p = strftime_add(p, pe, "\t");
2020-06-15 14:18:57 +00:00
continue;
case 'U':
p = strftime_conv(p, pe, (t->tm_yday + 7 - t->tm_wday) / 7, "%02d");
2020-06-15 14:18:57 +00:00
continue;
case 'u':
/*
** From Arnold Robbins' strftime version 3.0:
** "ISO 8601: Weekday as a decimal number
** [1 (Monday) - 7]"
** (ado, 5/24/93)
*/
p = strftime_conv(p, pe, (t->tm_wday == 0) ? 7 : t->tm_wday, "%d");
2020-06-15 14:18:57 +00:00
continue;
case 'V':
/*
** From Arnold Robbins' strftime version 3.0:
** "the week number of the year (the first
** Monday as the first day of week 1) as a
** decimal number (01-53). The method for
** determining the week number is as specified
** by ISO 8601 (to wit: if the week containing
** January 1 has four or more days in the new
** year, then it is week 1, otherwise it is
** week 53 of the previous year and the next
** week is week 1)."
** (ado, 5/24/93)
*/
/*
** XXX--If January 1 falls on a Friday,
** January 1-3 are part of week 53 of the
** previous year. By analogy, if January
** 1 falls on a Thursday, are December 29-31
** of the PREVIOUS year part of week 1???
** (ado 5/24/93)
**
** You are understood not to expect this.
*/
i = (t->tm_yday + 10 - (t->tm_wday ? (t->tm_wday - 1) : 6)) / 7;
if (i == 0) {
/*
** What day of the week does
** January 1 fall on?
*/
i = t->tm_wday - (t->tm_yday - 1);
/*
** Fri Jan 1: 53
** Sun Jan 1: 52
** Sat Jan 1: 53 if previous
** year a leap
** year, else 52
*/
if (i == TM_FRIDAY)
i = 53;
else if (i == TM_SUNDAY)
i = 52;
else
i = isleap(t->tm_year + TM_YEAR_BASE) ? 53 : 52;
#ifdef XPG4_1994_04_09
/*
** As of 4/9/94, though,
** XPG4 calls for 53
** unconditionally.
*/
i = 53;
#endif /* defined XPG4_1994_04_09 */
}
p = strftime_conv(p, pe, i, "%02d");
2020-06-15 14:18:57 +00:00
continue;
case 'v':
/*
** From Arnold Robbins' strftime version 3.0:
** "date as dd-bbb-YYYY"
** (ado, 5/24/93)
*/
p = strftime_timefmt(p, pe, "%e-%b-%Y", t);
2020-06-15 14:18:57 +00:00
continue;
case 'W':
p = strftime_conv(
p, pe, (t->tm_yday + 7 - (t->tm_wday ? (t->tm_wday - 1) : 6)) / 7,
2020-06-15 14:18:57 +00:00
"%02d");
continue;
case 'w':
p = strftime_conv(p, pe, t->tm_wday, "%d");
2020-06-15 14:18:57 +00:00
continue;
case 'y':
p = strftime_conv(p, pe, (t->tm_year + TM_YEAR_BASE) % 100, "%02d");
2020-06-15 14:18:57 +00:00
continue;
case 'Y':
p = strftime_conv(p, pe, t->tm_year + TM_YEAR_BASE, "%04d");
2020-06-15 14:18:57 +00:00
continue;
case 'Z':
if (t->tm_zone) {
p = strftime_add(p, pe, t->tm_zone);
2020-06-15 14:18:57 +00:00
} else {
if (t->tm_isdst == 0 || t->tm_isdst == 1) {
p = strftime_add(p, pe, tzname[t->tm_isdst]);
2020-06-15 14:18:57 +00:00
} else {
p = strftime_add(p, pe, "?");
2020-06-15 14:18:57 +00:00
}
}
continue;
case 'z':
if (t->tm_isdst < 0) continue;
#ifdef TM_GMTOFF
diff = t->TM_GMTOFF;
#else /* !defined TM_GMTOFF */
if (t->tm_isdst == 0)
#ifdef USG_COMPAT
diff = -timezone;
#else /* !defined USG_COMPAT */
continue;
#endif /* !defined USG_COMPAT */
else
#ifdef ALTZONE
diff = -altzone;
#else /* !defined ALTZONE */
continue;
#endif /* !defined ALTZONE */
#endif /* !defined TM_GMTOFF */
if (diff < 0) {
sign = "-";
diff = -diff;
} else {
sign = "+";
}
p = strftime_add(p, pe, sign);
2020-06-15 14:18:57 +00:00
diff /= SECSPERMIN;
diff = (diff / MINSPERHOUR) * 100 + (diff % MINSPERHOUR);
p = strftime_conv(p, pe, diff, "%04d");
2020-06-15 14:18:57 +00:00
continue;
case '%':
/*
* X311J/88-090 (4.12.3.5): if conversion char is
* undefined, behavior is undefined. Print out the
* character itself as printf(3) also does.
*/
default:
break;
}
}
if (p >= pe) break;
*p++ = *format;
2020-06-15 14:18:57 +00:00
}
return p;
2020-06-15 14:18:57 +00:00
}
/**
* Converts time to string, e.g.
*
* char b[64];
* int64_t sec;
* struct tm tm;
* time(&sec);
* localtime_r(&sec, &tm);
* strftime(b, sizeof(b), "%Y-%m-%dT%H:%M:%S%z", &tm); // ISO8601
* strftime(b, sizeof(b), "%a, %d %b %Y %H:%M:%S %Z", &tm); // RFC1123
*
* @return bytes copied excluding nul, or 0 on error
*/
size_t strftime(char *s, size_t size, const char *f, const struct tm *t) {
2020-06-15 14:18:57 +00:00
char *p;
assert(t);
p = strftime_timefmt(s, s + size, f, t);
if (p < s + size) {
*p = '\0';
return p - s;
} else {
s[size - 1] = '\0';
return 0;
}
2020-06-15 14:18:57 +00:00
}