aboutsummaryrefslogtreecommitdiff
path: root/lib/libc/stdtime
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libc/stdtime')
-rw-r--r--lib/libc/stdtime/Makefile.inc35
-rw-r--r--lib/libc/stdtime/Symbol.map43
-rw-r--r--lib/libc/stdtime/ctime.3402
-rw-r--r--lib/libc/stdtime/strftime.3287
-rw-r--r--lib/libc/stdtime/strftime.c629
-rw-r--r--lib/libc/stdtime/strptime.3185
-rw-r--r--lib/libc/stdtime/strptime.c709
-rw-r--r--lib/libc/stdtime/time32.c97
-rw-r--r--lib/libc/stdtime/timelocal.c153
-rw-r--r--lib/libc/stdtime/timelocal.h61
-rw-r--r--lib/libc/stdtime/tzset.3344
11 files changed, 2945 insertions, 0 deletions
diff --git a/lib/libc/stdtime/Makefile.inc b/lib/libc/stdtime/Makefile.inc
new file mode 100644
index 000000000000..647cbe6f40ba
--- /dev/null
+++ b/lib/libc/stdtime/Makefile.inc
@@ -0,0 +1,35 @@
+# Makefile.inc,v 1.2 1994/09/13 21:26:01 wollman Exp
+
+.PATH: ${LIBC_SRCTOP}/stdtime ${SRCTOP}/contrib/tzcode
+
+TZCODE_SRCS= asctime.c difftime.c localtime.c
+STDTIME_SRCS= strftime.c strptime.c timelocal.c
+SRCS+= ${TZCODE_SRCS} ${STDTIME_SRCS} time32.c
+
+SYM_MAPS+= ${LIBC_SRCTOP}/stdtime/Symbol.map
+
+.for src in ${TZCODE_SRCS} ${STDTIME_SRCS}
+CFLAGS.${src}+= -I${SRCTOP}/contrib/tzcode -include tzconfig.h
+.endfor
+.for src in ${STDTIME_SRCS}
+CFLAGS.${src}+= -I${LIBC_SRCTOP}/stdtime
+.endfor
+
+CFLAGS.localtime.c+= -DALL_STATE -DTHREAD_SAFE
+.if ${MK_DETECT_TZ_CHANGES} != "no"
+CFLAGS.localtime.c+= -DDETECT_TZ_CHANGES
+CFLAGS.Version.map+= -DDETECT_TZ_CHANGES
+.endif
+
+MAN+= ctime.3 strftime.3 strptime.3 time2posix.3 tzset.3
+MAN+= tzfile.5
+
+MLINKS+=ctime.3 asctime.3 ctime.3 difftime.3 ctime.3 gmtime.3 \
+ ctime.3 localtime.3 ctime.3 mktime.3 ctime.3 timegm.3 \
+ ctime.3 ctime_r.3 ctime.3 localtime_r.3 ctime.3 gmtime_r.3 \
+ ctime.3 asctime_r.3
+MLINKS+=strftime.3 strftime_l.3
+MLINKS+=strptime.3 strptime_l.3
+MLINKS+=time2posix.3 posix2time.3
+MLINKS+=tzset.3 daylight.3 \
+ tzset.3 timezone.3
diff --git a/lib/libc/stdtime/Symbol.map b/lib/libc/stdtime/Symbol.map
new file mode 100644
index 000000000000..6a34cd3ea590
--- /dev/null
+++ b/lib/libc/stdtime/Symbol.map
@@ -0,0 +1,43 @@
+FBSD_1.0 {
+ _time32_to_time;
+ _time_to_time32;
+ _time64_to_time;
+ _time_to_time64;
+ _time_to_long;
+ _long_to_time;
+ _time_to_int;
+ _int_to_time;
+ strptime;
+ strftime;
+ tzname;
+ tzsetwall;
+ tzset;
+ localtime;
+ localtime_r;
+ gmtime;
+ gmtime_r;
+ offtime;
+ offtime_r;
+ ctime;
+ ctime_r;
+ mktime;
+ timelocal;
+ timegm;
+ timeoff;
+ time2posix;
+ posix2time;
+ difftime;
+ asctime_r;
+ asctime;
+};
+
+FBSD_1.8 {
+ daylight;
+ timezone;
+};
+
+FBSDprivate_1.0 {
+#ifdef DETECT_TZ_CHANGES
+ __tz_change_interval;
+#endif
+};
diff --git a/lib/libc/stdtime/ctime.3 b/lib/libc/stdtime/ctime.3
new file mode 100644
index 000000000000..96b7f775535a
--- /dev/null
+++ b/lib/libc/stdtime/ctime.3
@@ -0,0 +1,402 @@
+.\" Copyright (c) 1989, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Arthur Olson.
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd March 26, 2024
+.Dt CTIME 3
+.Os
+.Sh NAME
+.Nm asctime ,
+.Nm asctime_r ,
+.Nm ctime ,
+.Nm ctime_r ,
+.Nm difftime ,
+.Nm gmtime ,
+.Nm gmtime_r ,
+.Nm localtime ,
+.Nm localtime_r ,
+.Nm mktime ,
+.Nm timegm
+.Nd transform binary date and time values
+.Sh LIBRARY
+.Lb libc
+.Sh SYNOPSIS
+.In time.h
+.Vt extern char *tzname[2] ;
+.Ft char *
+.Fn asctime "const struct tm *tm"
+.Ft char *
+.Fn asctime_r "const struct tm *tm" "char *buf"
+.Ft char *
+.Fn ctime "const time_t *clock"
+.Ft char *
+.Fn ctime_r "const time_t *clock" "char *buf"
+.Ft double
+.Fn difftime "time_t time1" "time_t time0"
+.Ft struct tm *
+.Fn gmtime "const time_t *clock"
+.Ft struct tm *
+.Fn gmtime_r "const time_t *clock" "struct tm *result"
+.Ft struct tm *
+.Fn localtime "const time_t *clock"
+.Ft struct tm *
+.Fn localtime_r "const time_t *clock" "struct tm *result"
+.Ft time_t
+.Fn mktime "struct tm *tm"
+.Ft time_t
+.Fn timegm "struct tm *tm"
+.Sh DESCRIPTION
+The
+.Fn ctime ,
+.Fn gmtime ,
+and
+.Fn localtime
+functions all take as argument a pointer to a time value representing
+the time in seconds since the Epoch (00:00:00 UTC on January 1, 1970;
+see
+.Xr time 3 ) .
+.Pp
+The
+.Fn localtime
+function converts the time value pointed to by
+.Fa clock ,
+and returns a pointer to a
+.Vt struct tm
+(described below) which contains
+the broken-out time information for the value after adjusting for the current
+time zone (see
+.Xr tzset 3 ) .
+When the specified time translates to a year that will not fit in an
+.Vt int ,
+.Fn localtime
+returns
+.Dv NULL .
+The
+.Fn localtime
+function uses
+.Xr tzset 3
+to initialize time conversion information if
+.Xr tzset 3
+has not already been called by the process.
+.Pp
+After filling in the
+.Vt struct tm ,
+.Fn localtime
+sets the
+.Va tm_isdst Ns 'th
+element of
+.Va tzname
+to a pointer to an ASCII string that is the time zone abbreviation to be
+used with
+.Fn localtime Ns 's
+return value.
+.Pp
+The
+.Fn gmtime
+function similarly converts the time value, but without any time zone
+adjustment, and returns a pointer to a
+.Vt struct tm .
+.Pp
+The
+.Fn ctime
+function
+adjusts the time value for the current time zone in the same manner as
+.Fn localtime ,
+and returns a pointer to a 26-character string of the form:
+.Bd -literal -offset indent
+Thu Nov 24 18:22:48 1986\en\e0
+.Ed
+.Pp
+All the fields have constant width.
+.Pp
+The
+.Fn asctime
+function converts the broken down time in the
+.Vt struct tm
+pointed to by
+.Fa tm
+to the form shown in the example above.
+.Pp
+The
+.Fn ctime_r
+and
+.Fn asctime_r
+functions
+provide the same functionality as
+.Fn ctime
+and
+.Fn asctime
+except the caller must provide the output buffer
+.Fa buf ,
+which must be at least 26 characters long, to store the result in.
+The
+.Fn localtime_r
+and
+.Fn gmtime_r
+functions provide the same functionality as
+.Fn localtime
+and
+.Fn gmtime
+respectively, except the caller must provide the output buffer
+.Fa result .
+.Pp
+The functions
+.Fn mktime
+and
+.Fn timegm
+convert the broken-down time in the
+.Vt struct tm
+pointed to by
+.Fa tm
+into a time value with the same encoding as that of the values
+returned by the
+.Xr time 3
+function (that is, seconds from the Epoch, UTC).
+The
+.Fn mktime
+function interprets the input structure according to the current
+timezone setting (see
+.Xr tzset 3 )
+while the
+.Fn timegm
+function interprets the input structure as representing Universal
+Coordinated Time
+.Pq UTC .
+.Pp
+The original values of the
+.Fa tm_wday
+and
+.Fa tm_yday
+components of the structure are ignored, and the original values of the
+other components are not restricted to their normal ranges, and will be
+normalized if needed.
+For example,
+October 40 is changed into November 9,
+a
+.Fa tm_hour
+of \-1 means 1 hour before midnight,
+.Fa tm_mday
+of 0 means the day preceding the current month, and
+.Fa tm_mon
+of \-2 means 2 months before January of
+.Fa tm_year .
+(A positive or zero value for
+.Fa tm_isdst
+causes
+.Fn mktime
+to presume initially that summer time (for example, Daylight Saving Time)
+is or is not in effect for the specified time, respectively.
+A negative value for
+.Fa tm_isdst
+causes the
+.Fn mktime
+function to attempt to guess whether summer time is in effect for the
+specified time.
+The
+.Fa tm_isdst
+and
+.Fa tm_gmtoff
+members are forced to zero by
+.Fn timegm . )
+.Pp
+On successful completion, the values of the
+.Fa tm_wday
+and
+.Fa tm_yday
+components of the structure are set appropriately, and the other components
+are set to represent the specified calendar time, but with their values
+forced to their normal ranges; the final value of
+.Fa tm_mday
+is not set until
+.Fa tm_mon
+and
+.Fa tm_year
+are determined.
+The
+.Fn mktime
+function
+returns the specified calendar time; if the calendar time cannot be
+represented, it returns \-1 and sets
+.Xr errno 3
+to an appropriate value.
+.Pp
+Note that \-1 is a valid result (representing one second before
+midnight UTC on the evening of 31 December 1969), so this cannot be
+relied upon to indicate success or failure; instead,
+.Fa tm_wday
+and / or
+.Fa tm_yday
+should be set to an out-of-bounds value (e.g. \-1) prior to calling
+.Fn mktime
+or
+.Fn timegm
+and checked after the call.
+.Pp
+The
+.Fn difftime
+function returns the difference in seconds between two time values,
+.Fa time1
+\-
+.Fa time0 .
+.Pp
+External declarations as well as the definition of
+.Vt struct tm
+are in the
+.In time.h
+header.
+The
+.Vt tm
+structure includes at least the following fields:
+.Bd -literal -offset indent
+int tm_sec; /* seconds (0 - 60) */
+int tm_min; /* minutes (0 - 59) */
+int tm_hour; /* hours (0 - 23) */
+int tm_mday; /* day of month (1 - 31) */
+int tm_mon; /* month of year (0 - 11) */
+int tm_year; /* year \- 1900 */
+int tm_wday; /* day of week (Sunday = 0) */
+int tm_yday; /* day of year (0 - 365) */
+int tm_isdst; /* is summer time in effect? */
+char *tm_zone; /* abbreviation of timezone name */
+long tm_gmtoff; /* offset from UTC in seconds */
+.Ed
+.Pp
+The
+.Fa tm_isdst
+field is non-zero if summer time is in effect.
+.Pp
+The
+.Fa tm_gmtoff
+field is the offset in seconds of the time represented from UTC,
+with positive values indicating a time zone ahead of UTC (east of the
+Prime Meridian).
+.Sh SEE ALSO
+.Xr date 1 ,
+.Xr clock_gettime 2 ,
+.Xr gettimeofday 2 ,
+.Xr getenv 3 ,
+.Xr time 3 ,
+.Xr tzset 3 ,
+.Xr tzfile 5
+.Sh STANDARDS
+The
+.Fn asctime ,
+.Fn ctime ,
+.Fn difftime ,
+.Fn gmtime ,
+.Fn localtime ,
+and
+.Fn mktime
+functions conform to
+.St -isoC ,
+and conform to
+.St -p1003.1-96
+provided the selected local timezone does not contain a leap-second table
+(see
+.Xr zic 8 ) .
+.Pp
+The
+.Fn asctime_r ,
+.Fn ctime_r ,
+.Fn gmtime_r ,
+and
+.Fn localtime_r
+functions are expected to conform to
+.St -p1003.1-96
+(again provided the selected local timezone does not contain a leap-second
+table).
+.Pp
+The
+.Fn timegm
+function is not specified by any standard; its function cannot be
+completely emulated using the standard functions described above.
+.Sh HISTORY
+This manual page is derived from
+the time package contributed to Berkeley by
+.An Arthur Olson
+and which appeared in
+.Bx 4.3 .
+.Pp
+The functions
+.Fn asctime ,
+.Fn gmtime ,
+and
+.Fn localtime
+first appeared in
+.At v5 ,
+.Fn difftime
+and
+.Fn mktime
+in
+.Bx 4.3 Reno ,
+and
+.Fn timegm
+and
+.Fn timelocal
+in SunOS 4.0.
+.Pp
+The
+.Fn asctime_r ,
+.Fn ctime_r ,
+.Fn gmtime_r
+and
+.Fn localtime_r
+functions have been available since
+.Fx 8.0 .
+.Sh BUGS
+Except for
+.Fn difftime ,
+.Fn mktime ,
+and the
+.Fn \&_r
+variants of the other functions,
+these functions leave their result in an internal static object and return
+a pointer to that object.
+Subsequent calls to these
+function will modify the same object.
+.Pp
+The C Standard provides no mechanism for a program to modify its current
+local timezone setting, and the POSIX-standard
+method is not reentrant.
+(However, thread-safe implementations are provided
+in the POSIX threaded environment.)
+.Pp
+The
+.Va tm_zone
+field of a returned
+.Vt tm
+structure points to a static array of characters,
+which will also be overwritten by any subsequent calls (as well as by
+subsequent calls to
+.Xr tzset 3 ) .
+.Pp
+Use of the external variable
+.Fa tzname
+is discouraged; the
+.Fa tm_zone
+entry in the tm structure is preferred.
diff --git a/lib/libc/stdtime/strftime.3 b/lib/libc/stdtime/strftime.3
new file mode 100644
index 000000000000..f46eee525900
--- /dev/null
+++ b/lib/libc/stdtime/strftime.3
@@ -0,0 +1,287 @@
+.\" Copyright (c) 1989, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the American National Standards Committee X3, on Information
+.\" Processing Systems.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd June 25, 2012
+.Dt STRFTIME 3
+.Os
+.Sh NAME
+.Nm strftime
+.Nd format date and time
+.Sh LIBRARY
+.Lb libc
+.Sh SYNOPSIS
+.In time.h
+.Ft size_t
+.Fo strftime
+.Fa "char * restrict buf"
+.Fa "size_t maxsize"
+.Fa "const char * restrict format"
+.Fa "const struct tm * restrict timeptr"
+.Fc
+.Ft size_t
+.Fn strftime_l "char *restrict buf" "size_t maxsize" "const char * restrict format" "const struct tm *restrict timeptr" "locale_t loc"
+.Sh DESCRIPTION
+The
+.Fn strftime
+function formats the information from
+.Fa timeptr
+into the buffer
+.Fa buf
+according to the string pointed to by
+.Fa format .
+The function
+.Fn strftime_l
+does the same as
+.Fn strftime
+but takes an explicit locale rather than using the current locale.
+.Pp
+The
+.Fa format
+string consists of zero or more conversion specifications and
+ordinary characters.
+All ordinary characters are copied directly into the buffer.
+A conversion specification consists of a percent sign
+.Dq Ql %
+and one other character.
+.Pp
+No more than
+.Fa maxsize
+characters will be placed into the array.
+If the total number of resulting characters, including the terminating
+NUL character, is not more than
+.Fa maxsize ,
+.Fn strftime
+returns the number of characters in the array, not counting the
+terminating NUL.
+Otherwise, zero is returned and the buffer contents are indeterminate.
+.Pp
+The conversion specifications are copied to the buffer after expansion
+as follows:
+.Bl -tag -width "xxxx"
+.It Cm \&%A
+is replaced by national representation of the full weekday name.
+.It Cm %a
+is replaced by national representation of
+the abbreviated weekday name.
+.It Cm \&%B
+is replaced by national representation of the full month name.
+.It Cm %b
+is replaced by national representation of
+the abbreviated month name.
+.It Cm \&%C
+is replaced by (year / 100) as decimal number; single
+digits are preceded by a zero.
+.It Cm %c
+is replaced by national representation of time and date.
+.It Cm \&%D
+is equivalent to
+.Dq Li %m/%d/%y .
+.It Cm %d
+is replaced by the day of the month as a decimal number (01-31).
+.It Cm %E* %O*
+POSIX locale extensions.
+The sequences
+%Ec %EC %Ex %EX %Ey %EY
+%Od %Oe %OH %OI %Om %OM
+%OS %Ou %OU %OV %Ow %OW %Oy
+are supposed to provide alternate
+representations.
+.Pp
+Additionally %OB implemented
+to represent alternative months names
+(used standalone, without day mentioned).
+.It Cm %e
+is replaced by the day of the month as a decimal number (1-31); single
+digits are preceded by a blank.
+.It Cm \&%F
+is equivalent to
+.Dq Li %Y-%m-%d .
+.It Cm \&%G
+is replaced by a year as a decimal number with century.
+This year is the one that contains the greater part of
+the week (Monday as the first day of the week).
+.It Cm %g
+is replaced by the same year as in
+.Dq Li %G ,
+but as a decimal number without century (00-99).
+.It Cm \&%H
+is replaced by the hour (24-hour clock) as a decimal number (00-23).
+.It Cm %h
+the same as
+.Cm %b .
+.It Cm \&%I
+is replaced by the hour (12-hour clock) as a decimal number (01-12).
+.It Cm %j
+is replaced by the day of the year as a decimal number (001-366).
+.It Cm %k
+is replaced by the hour (24-hour clock) as a decimal number (0-23);
+single digits are preceded by a blank.
+.It Cm %l
+is replaced by the hour (12-hour clock) as a decimal number (1-12);
+single digits are preceded by a blank.
+.It Cm \&%M
+is replaced by the minute as a decimal number (00-59).
+.It Cm %m
+is replaced by the month as a decimal number (01-12).
+.It Cm %n
+is replaced by a newline.
+.It Cm %O*
+the same as
+.Cm %E* .
+.It Cm %p
+is replaced by national representation of either
+"ante meridiem" (a.m.)
+or
+"post meridiem" (p.m.)
+as appropriate.
+.It Cm \&%R
+is equivalent to
+.Dq Li %H:%M .
+.It Cm %r
+is equivalent to
+.Dq Li %I:%M:%S %p .
+.It Cm \&%S
+is replaced by the second as a decimal number (00-60).
+.It Cm %s
+is replaced by the number of seconds since the Epoch, UTC (see
+.Xr mktime 3 ) .
+.It Cm \&%T
+is equivalent to
+.Dq Li %H:%M:%S .
+.It Cm %t
+is replaced by a tab.
+.It Cm \&%U
+is replaced by the week number of the year (Sunday as the first day of
+the week) as a decimal number (00-53).
+.It Cm %u
+is replaced by the weekday (Monday as the first day of the week)
+as a decimal number (1-7).
+.It Cm \&%V
+is replaced by the week number of the year (Monday as the first day of
+the week) as a decimal number (01-53).
+If the week containing January
+1 has four or more days in the new year, then it is week 1; otherwise
+it is the last week of the previous year, and the next week is week 1.
+.It Cm %v
+is equivalent to
+.Dq Li %e-%b-%Y .
+.It Cm \&%W
+is replaced by the week number of the year (Monday as the first day of
+the week) as a decimal number (00-53).
+.It Cm %w
+is replaced by the weekday (Sunday as the first day of the week)
+as a decimal number (0-6).
+.It Cm \&%X
+is replaced by national representation of the time.
+.It Cm %x
+is replaced by national representation of the date.
+.It Cm \&%Y
+is replaced by the year with century as a decimal number.
+.It Cm %y
+is replaced by the year without century as a decimal number (00-99).
+.It Cm \&%Z
+is replaced by the time zone name.
+.It Cm %z
+is replaced by the time zone offset from UTC; a leading plus sign stands for
+east of UTC, a minus sign for west of UTC, hours and minutes follow
+with two digits each and no delimiter between them (common form for
+RFC 822 date headers).
+.It Cm %+
+is replaced by national representation of the date and time
+(the format is similar to that produced by
+.Xr date 1 ) .
+.It Cm %-*
+GNU libc extension.
+Do not do any padding when performing numerical outputs.
+.It Cm %_*
+GNU libc extension.
+Explicitly specify space for padding.
+.It Cm %0*
+GNU libc extension.
+Explicitly specify zero for padding.
+.It Cm %%
+is replaced by
+.Ql % .
+.El
+.Sh SEE ALSO
+.Xr date 1 ,
+.Xr printf 1 ,
+.Xr ctime 3 ,
+.Xr printf 3 ,
+.Xr strptime 3 ,
+.Xr wcsftime 3
+.Sh STANDARDS
+The
+.Fn strftime
+function
+conforms to
+.St -isoC
+with a lot of extensions including
+.Ql \&%C ,
+.Ql \&%D ,
+.Ql %E* ,
+.Ql %e ,
+.Ql %G ,
+.Ql %g ,
+.Ql %h ,
+.Ql %k ,
+.Ql %l ,
+.Ql %n ,
+.Ql %O* ,
+.Ql \&%R ,
+.Ql %r ,
+.Ql %s ,
+.Ql \&%T ,
+.Ql %t ,
+.Ql %u ,
+.Ql \&%V ,
+.Ql %z ,
+.Ql %+ .
+.Pp
+The peculiar week number and year in the replacements of
+.Ql %G ,
+.Ql %g
+and
+.Ql \&%V
+are defined in
+.St -iso8601 .
+The
+.Fn strftime_l
+function conforms to
+.St -p1003.1-2008 .
+.Sh BUGS
+There is no conversion specification for the phase of the moon.
+.Pp
+The
+.Fn strftime
+function does not correctly handle multibyte characters in the
+.Fa format
+argument.
diff --git a/lib/libc/stdtime/strftime.c b/lib/libc/stdtime/strftime.c
new file mode 100644
index 000000000000..89c9e4cf88ff
--- /dev/null
+++ b/lib/libc/stdtime/strftime.c
@@ -0,0 +1,629 @@
+/*
+ * SPDX-License-Identifier: BSD-4.3TAHOE
+ *
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Copyright (c) 2011 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by David Chisnall
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * 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 "namespace.h"
+#include "private.h"
+
+#include "tzfile.h"
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include "un-namespace.h"
+#include "timelocal.h"
+
+static char * _add(const char *, char *, const char *);
+static char * _conv(int, const char *, char *, const char *, locale_t);
+static char * _fmt(const char *, const struct tm *, char *, const char *,
+ int *, locale_t);
+static char * _yconv(int, int, int, int, char *, const char *, locale_t);
+
+extern char * tzname[];
+
+#ifndef YEAR_2000_NAME
+#define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
+#endif /* !defined YEAR_2000_NAME */
+
+#define IN_NONE 0
+#define IN_SOME 1
+#define IN_THIS 2
+#define IN_ALL 3
+
+#define PAD_DEFAULT 0
+#define PAD_LESS 1
+#define PAD_SPACE 2
+#define PAD_ZERO 3
+
+static const char fmt_padding[][4][5] = {
+ /* DEFAULT, LESS, SPACE, ZERO */
+#define PAD_FMT_MONTHDAY 0
+#define PAD_FMT_HMS 0
+#define PAD_FMT_CENTURY 0
+#define PAD_FMT_SHORTYEAR 0
+#define PAD_FMT_MONTH 0
+#define PAD_FMT_WEEKOFYEAR 0
+#define PAD_FMT_DAYOFMONTH 0
+ { "%02d", "%d", "%2d", "%02d" },
+#define PAD_FMT_SDAYOFMONTH 1
+#define PAD_FMT_SHMS 1
+ { "%2d", "%d", "%2d", "%02d" },
+#define PAD_FMT_DAYOFYEAR 2
+ { "%03d", "%d", "%3d", "%03d" },
+#define PAD_FMT_YEAR 3
+ { "%04d", "%d", "%4d", "%04d" }
+};
+
+size_t
+strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format,
+ const struct tm * __restrict t, locale_t loc)
+{
+ char * p;
+ int warn;
+ FIX_LOCALE(loc);
+
+ tzset();
+ warn = IN_NONE;
+ p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn, loc);
+#ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU
+ if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) {
+ (void) fprintf_l(stderr, loc, "\n");
+ if (format == NULL)
+ (void) fputs("NULL strftime format ", stderr);
+ else (void) fprintf_l(stderr, loc, "strftime format \"%s\" ",
+ format);
+ (void) fputs("yields only two digits of years in ", stderr);
+ if (warn == IN_SOME)
+ (void) fputs("some locales", stderr);
+ else if (warn == IN_THIS)
+ (void) fputs("the current locale", stderr);
+ else (void) fputs("all locales", stderr);
+ (void) fputs("\n", stderr);
+ }
+#endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */
+ if (p == s + maxsize)
+ return (0);
+ *p = '\0';
+ return p - s;
+}
+
+size_t
+strftime(char * __restrict s, size_t maxsize, const char * __restrict format,
+ const struct tm * __restrict t)
+{
+ return strftime_l(s, maxsize, format, t, __get_locale());
+}
+
+static char *
+_fmt(const char *format, const struct tm * const t, char *pt,
+ const char * const ptlim, int *warnp, locale_t loc)
+{
+ int Ealternative, Oalternative, PadIndex;
+ struct lc_time_T *tptr = __get_current_time_locale(loc);
+
+ for ( ; *format; ++format) {
+ if (*format == '%') {
+ Ealternative = 0;
+ Oalternative = 0;
+ PadIndex = PAD_DEFAULT;
+label:
+ switch (*++format) {
+ case '\0':
+ --format;
+ break;
+ case 'A':
+ pt = _add((t->tm_wday < 0 ||
+ t->tm_wday >= DAYSPERWEEK) ?
+ "?" : tptr->weekday[t->tm_wday],
+ pt, ptlim);
+ continue;
+ case 'a':
+ pt = _add((t->tm_wday < 0 ||
+ t->tm_wday >= DAYSPERWEEK) ?
+ "?" : tptr->wday[t->tm_wday],
+ pt, ptlim);
+ continue;
+ case 'B':
+ pt = _add((t->tm_mon < 0 ||
+ t->tm_mon >= MONSPERYEAR) ?
+ "?" : (Oalternative ? tptr->alt_month :
+ tptr->month)[t->tm_mon],
+ pt, ptlim);
+ continue;
+ case 'b':
+ case 'h':
+ pt = _add((t->tm_mon < 0 ||
+ t->tm_mon >= MONSPERYEAR) ?
+ "?" : tptr->mon[t->tm_mon],
+ pt, ptlim);
+ continue;
+ case 'C':
+ /*
+ * %C used to do a...
+ * _fmt("%a %b %e %X %Y", t);
+ * ...whereas now POSIX 1003.2 calls for
+ * something completely different.
+ * (ado, 1993-05-24)
+ */
+ pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0,
+ pt, ptlim, loc);
+ continue;
+ case 'c':
+ {
+ int warn2 = IN_SOME;
+
+ pt = _fmt(tptr->c_fmt, t, pt, ptlim, &warn2, loc);
+ if (warn2 == IN_ALL)
+ warn2 = IN_THIS;
+ if (warn2 > *warnp)
+ *warnp = warn2;
+ }
+ continue;
+ case 'D':
+ pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp, loc);
+ continue;
+ case 'd':
+ pt = _conv(t->tm_mday,
+ fmt_padding[PAD_FMT_DAYOFMONTH][PadIndex],
+ pt, ptlim, loc);
+ continue;
+ case 'E':
+ if (Ealternative || Oalternative)
+ break;
+ Ealternative++;
+ goto label;
+ case 'O':
+ /*
+ * C99 locale modifiers.
+ * The sequences
+ * %Ec %EC %Ex %EX %Ey %EY
+ * %Od %oe %OH %OI %Om %OM
+ * %OS %Ou %OU %OV %Ow %OW %Oy
+ * are supposed to provide alternate
+ * representations.
+ *
+ * FreeBSD extension
+ * %OB
+ */
+ if (Ealternative || Oalternative)
+ break;
+ Oalternative++;
+ goto label;
+ case 'e':
+ pt = _conv(t->tm_mday,
+ fmt_padding[PAD_FMT_SDAYOFMONTH][PadIndex],
+ pt, ptlim, loc);
+ continue;
+ case 'F':
+ pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp, loc);
+ continue;
+ case 'H':
+ pt = _conv(t->tm_hour, fmt_padding[PAD_FMT_HMS][PadIndex],
+ pt, ptlim, loc);
+ continue;
+ case 'I':
+ pt = _conv((t->tm_hour % 12) ?
+ (t->tm_hour % 12) : 12,
+ fmt_padding[PAD_FMT_HMS][PadIndex],
+ pt, ptlim, loc);
+ continue;
+ case 'j':
+ pt = _conv(t->tm_yday + 1,
+ fmt_padding[PAD_FMT_DAYOFYEAR][PadIndex],
+ pt, ptlim, loc);
+ continue;
+ case 'k':
+ /*
+ * This used to be...
+ * _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, 1993-05-24)
+ */
+ pt = _conv(t->tm_hour, fmt_padding[PAD_FMT_SHMS][PadIndex],
+ pt, ptlim, loc);
+ continue;
+#ifdef KITCHEN_SINK
+ case 'K':
+ /*
+ ** After all this time, still unclaimed!
+ */
+ pt = _add("kitchen sink", pt, ptlim);
+ continue;
+#endif /* defined KITCHEN_SINK */
+ case 'l':
+ /*
+ * This used to be...
+ * _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, 1993-05-24)
+ */
+ pt = _conv((t->tm_hour % 12) ?
+ (t->tm_hour % 12) : 12,
+ fmt_padding[PAD_FMT_SHMS][PadIndex],
+ pt, ptlim, loc);
+ continue;
+ case 'M':
+ pt = _conv(t->tm_min, fmt_padding[PAD_FMT_HMS][PadIndex],
+ pt, ptlim, loc);
+ continue;
+ case 'm':
+ pt = _conv(t->tm_mon + 1,
+ fmt_padding[PAD_FMT_MONTH][PadIndex],
+ pt, ptlim, loc);
+ continue;
+ case 'n':
+ pt = _add("\n", pt, ptlim);
+ continue;
+ case 'p':
+ pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
+ tptr->pm : tptr->am,
+ pt, ptlim);
+ continue;
+ case 'R':
+ pt = _fmt("%H:%M", t, pt, ptlim, warnp, loc);
+ continue;
+ case 'r':
+ pt = _fmt(tptr->ampm_fmt, t, pt, ptlim,
+ warnp, loc);
+ continue;
+ case 'S':
+ pt = _conv(t->tm_sec, fmt_padding[PAD_FMT_HMS][PadIndex],
+ pt, ptlim, loc);
+ continue;
+ case 's':
+ {
+ struct tm tm;
+ char buf[INT_STRLEN_MAXIMUM(
+ time_t) + 1];
+ time_t mkt;
+
+ tm = *t;
+ mkt = timeoff(&tm, t->tm_gmtoff);
+ if (TYPE_SIGNED(time_t))
+ (void) sprintf_l(buf, loc, "%ld",
+ (long) mkt);
+ else (void) sprintf_l(buf, loc, "%lu",
+ (unsigned long) mkt);
+ pt = _add(buf, pt, ptlim);
+ }
+ continue;
+ case 'T':
+ pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp, loc);
+ continue;
+ case 't':
+ pt = _add("\t", pt, ptlim);
+ continue;
+ case 'U':
+ pt = _conv((t->tm_yday + DAYSPERWEEK -
+ t->tm_wday) / DAYSPERWEEK,
+ fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
+ pt, ptlim, loc);
+ continue;
+ case 'u':
+ /*
+ * From Arnold Robbins' strftime version 3.0:
+ * "ISO 8601: Weekday as a decimal number
+ * [1 (Monday) - 7]"
+ * (ado, 1993-05-24)
+ */
+ pt = _conv((t->tm_wday == 0) ?
+ DAYSPERWEEK : t->tm_wday,
+ "%d", pt, ptlim, loc);
+ continue;
+ case 'V': /* ISO 8601 week number */
+ case 'G': /* ISO 8601 year (four digits) */
+ case 'g': /* ISO 8601 year (two digits) */
+/*
+ * 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)."
+ * (ado, 1993-05-24)
+ *
+ * From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
+ * "Week 01 of a year is per definition the first week which has the
+ * Thursday in this year, which is equivalent to the week which contains
+ * the fourth day of January. In other words, the first week of a new year
+ * is the week which has the majority of its days in the new year. Week 01
+ * might also contain days from the previous year and the week before week
+ * 01 of a year is the last week (52 or 53) of the previous year even if
+ * it contains days from the new year. A week starts with Monday (day 1)
+ * and ends with Sunday (day 7). For example, the first week of the year
+ * 1997 lasts from 1996-12-30 to 1997-01-05..."
+ * (ado, 1996-01-02)
+ */
+ {
+ int year;
+ int base;
+ int yday;
+ int wday;
+ int w;
+
+ year = t->tm_year;
+ base = TM_YEAR_BASE;
+ yday = t->tm_yday;
+ wday = t->tm_wday;
+ for ( ; ; ) {
+ int len;
+ int bot;
+ int top;
+
+ len = isleap_sum(year, base) ?
+ DAYSPERLYEAR :
+ DAYSPERNYEAR;
+ /*
+ * What yday (-3 ... 3) does
+ * the ISO year begin on?
+ */
+ bot = ((yday + 11 - wday) %
+ DAYSPERWEEK) - 3;
+ /*
+ * What yday does the NEXT
+ * ISO year begin on?
+ */
+ top = bot -
+ (len % DAYSPERWEEK);
+ if (top < -3)
+ top += DAYSPERWEEK;
+ top += len;
+ if (yday >= top) {
+ ++base;
+ w = 1;
+ break;
+ }
+ if (yday >= bot) {
+ w = 1 + ((yday - bot) /
+ DAYSPERWEEK);
+ break;
+ }
+ --base;
+ yday += isleap_sum(year, base) ?
+ DAYSPERLYEAR :
+ DAYSPERNYEAR;
+ }
+#ifdef XPG4_1994_04_09
+ if ((w == 52 &&
+ t->tm_mon == TM_JANUARY) ||
+ (w == 1 &&
+ t->tm_mon == TM_DECEMBER))
+ w = 53;
+#endif /* defined XPG4_1994_04_09 */
+ if (*format == 'V')
+ pt = _conv(w, fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
+ pt, ptlim, loc);
+ else if (*format == 'g') {
+ *warnp = IN_ALL;
+ pt = _yconv(year, base, 0, 1,
+ pt, ptlim, loc);
+ } else pt = _yconv(year, base, 1, 1,
+ pt, ptlim, loc);
+ }
+ continue;
+ case 'v':
+ /*
+ * From Arnold Robbins' strftime version 3.0:
+ * "date as dd-bbb-YYYY"
+ * (ado, 1993-05-24)
+ */
+ pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp, loc);
+ continue;
+ case 'W':
+ pt = _conv((t->tm_yday + DAYSPERWEEK -
+ (t->tm_wday ?
+ (t->tm_wday - 1) :
+ (DAYSPERWEEK - 1))) / DAYSPERWEEK,
+ fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
+ pt, ptlim, loc);
+ continue;
+ case 'w':
+ pt = _conv(t->tm_wday, "%d", pt, ptlim, loc);
+ continue;
+ case 'X':
+ pt = _fmt(tptr->X_fmt, t, pt, ptlim, warnp, loc);
+ continue;
+ case 'x':
+ {
+ int warn2 = IN_SOME;
+
+ pt = _fmt(tptr->x_fmt, t, pt, ptlim, &warn2, loc);
+ if (warn2 == IN_ALL)
+ warn2 = IN_THIS;
+ if (warn2 > *warnp)
+ *warnp = warn2;
+ }
+ continue;
+ case 'y':
+ *warnp = IN_ALL;
+ pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1,
+ pt, ptlim, loc);
+ continue;
+ case 'Y':
+ pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1,
+ pt, ptlim, loc);
+ continue;
+ case 'Z':
+#ifdef TM_ZONE
+ if (t->TM_ZONE != NULL)
+ pt = _add(t->TM_ZONE, pt, ptlim);
+ else
+#endif /* defined TM_ZONE */
+ if (t->tm_isdst >= 0)
+ pt = _add(tzname[t->tm_isdst != 0],
+ pt, ptlim);
+ /*
+ * C99 says that %Z must be replaced by the
+ * empty string if the time zone is not
+ * determinable.
+ */
+ continue;
+ case 'z':
+ {
+ int diff;
+ char const * sign;
+
+ if (t->tm_isdst < 0)
+ continue;
+#ifdef TM_GMTOFF
+ diff = t->TM_GMTOFF;
+#else /* !defined TM_GMTOFF */
+ /*
+ * C99 says that the UTC offset must
+ * be computed by looking only at
+ * tm_isdst. This requirement is
+ * incorrect, since it means the code
+ * must rely on magic (in this case
+ * altzone and timezone), and the
+ * magic might not have the correct
+ * offset. Doing things correctly is
+ * tricky and requires disobeying C99;
+ * see GNU C strftime for details.
+ * For now, punt and conform to the
+ * standard, even though it's incorrect.
+ *
+ * C99 says that %z must be replaced by the
+ * empty string if the time zone is not
+ * determinable, so output nothing if the
+ * appropriate variables are not available.
+ */
+ 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 = "+";
+ pt = _add(sign, pt, ptlim);
+ diff /= SECSPERMIN;
+ diff = (diff / MINSPERHOUR) * 100 +
+ (diff % MINSPERHOUR);
+ pt = _conv(diff,
+ fmt_padding[PAD_FMT_YEAR][PadIndex],
+ pt, ptlim, loc);
+ }
+ continue;
+ case '+':
+ pt = _fmt(tptr->date_fmt, t, pt, ptlim,
+ warnp, loc);
+ continue;
+ case '-':
+ if (PadIndex != PAD_DEFAULT)
+ break;
+ PadIndex = PAD_LESS;
+ goto label;
+ case '_':
+ if (PadIndex != PAD_DEFAULT)
+ break;
+ PadIndex = PAD_SPACE;
+ goto label;
+ case '0':
+ if (PadIndex != PAD_DEFAULT)
+ break;
+ PadIndex = PAD_ZERO;
+ goto label;
+ 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 (pt == ptlim)
+ break;
+ *pt++ = *format;
+ }
+ return (pt);
+}
+
+static char *
+_conv(const int n, const char * const format, char * const pt,
+ const char * const ptlim, locale_t loc)
+{
+ char buf[INT_STRLEN_MAXIMUM(int) + 1];
+
+ (void) sprintf_l(buf, loc, format, n);
+ return _add(buf, pt, ptlim);
+}
+
+static char *
+_add(const char *str, char *pt, const char * const ptlim)
+{
+ while (pt < ptlim && (*pt = *str++) != '\0')
+ ++pt;
+ return (pt);
+}
+
+/*
+ * POSIX and the C Standard are unclear or inconsistent about
+ * what %C and %y do if the year is negative or exceeds 9999.
+ * Use the convention that %C concatenated with %y yields the
+ * same output as %Y, and that %Y contains at least 4 bytes,
+ * with more only if necessary.
+ */
+
+static char *
+_yconv(const int a, const int b, const int convert_top, const int convert_yy,
+ char *pt, const char * const ptlim, locale_t loc)
+{
+ register int lead;
+ register int trail;
+
+#define DIVISOR 100
+ trail = a % DIVISOR + b % DIVISOR;
+ lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
+ trail %= DIVISOR;
+ if (trail < 0 && lead > 0) {
+ trail += DIVISOR;
+ --lead;
+ } else if (lead < 0 && trail > 0) {
+ trail -= DIVISOR;
+ ++lead;
+ }
+ if (convert_top) {
+ if (lead == 0 && trail < 0)
+ pt = _add("-0", pt, ptlim);
+ else pt = _conv(lead, "%02d", pt, ptlim, loc);
+ }
+ if (convert_yy)
+ pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt,
+ ptlim, loc);
+ return (pt);
+}
diff --git a/lib/libc/stdtime/strptime.3 b/lib/libc/stdtime/strptime.3
new file mode 100644
index 000000000000..7df73d2d080a
--- /dev/null
+++ b/lib/libc/stdtime/strptime.3
@@ -0,0 +1,185 @@
+.\"
+.\" Copyright (c) 1997 Joerg Wunsch
+.\"
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE DEVELOPERS BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\" "
+.Dd December 9, 2024
+.Dt STRPTIME 3
+.Os
+.Sh NAME
+.Nm strptime
+.Nd parse date and time string
+.Sh LIBRARY
+.Lb libc
+.Sh SYNOPSIS
+.In time.h
+.Ft char *
+.Fo strptime
+.Fa "const char * restrict buf"
+.Fa "const char * restrict format"
+.Fa "struct tm * restrict timeptr"
+.Fc
+.In time.h
+.In xlocale.h
+.Ft char *
+.Fn strptime_l "const char * restrict buf" "const char * restrict format" "struct tm * restrict timeptr" "locale_t loc"
+.Sh DESCRIPTION
+The
+.Fn strptime
+function parses the string in the buffer
+.Fa buf
+according to the string pointed to by
+.Fa format ,
+and fills in the elements of the structure pointed to by
+.Fa timeptr .
+The resulting values will be relative to the local time zone.
+Thus, it can be considered the reverse operation of
+.Xr strftime 3 .
+The
+.Fn strptime_l
+function does the same as
+.Fn strptime ,
+but takes an explicit locale rather than using the current locale.
+.Pp
+The
+.Fa format
+string consists of zero or more conversion specifications and
+ordinary characters.
+All ordinary characters are matched exactly with the buffer, where
+white space in the format string will match any amount of white space
+in the buffer.
+All conversion specifications are identical to those described in
+.Xr strftime 3 .
+.Pp
+Two-digit year values, including formats
+.Fa %y
+and
+.Fa \&%D ,
+are now interpreted as beginning at 1969 per POSIX requirements.
+Years 69-00 are interpreted in the 20th century (1969-2000), years
+01-68 in the 21st century (2001-2068).
+The
+.Fa \&%U
+and
+.Fa %W
+format specifiers accept any value within the range 00 to 53.
+.Pp
+If the
+.Fa format
+string does not contain enough conversion specifications to completely
+specify the resulting
+.Vt struct tm ,
+the unspecified members of
+.Va timeptr
+are left untouched.
+For example, if
+.Fa format
+is
+.Dq Li "%H:%M:%S" ,
+only
+.Va tm_hour ,
+.Va tm_sec
+and
+.Va tm_min
+will be modified.
+If time relative to today is desired, initialize the
+.Fa timeptr
+structure with today's date before passing it to
+.Fn strptime .
+.Sh RETURN VALUES
+Upon successful completion,
+.Fn strptime
+returns the pointer to the first character in
+.Fa buf
+that has not been required to satisfy the specified conversions in
+.Fa format .
+It returns
+.Dv NULL
+if one of the conversions failed.
+.Fn strptime_l
+returns the same values as
+.Fn strptime .
+.Sh SEE ALSO
+.Xr date 1 ,
+.Xr scanf 3 ,
+.Xr strftime 3
+.Sh HISTORY
+The
+.Fn strptime
+function appeared in
+.Fx 3.0 .
+.Sh AUTHORS
+The
+.Fn strptime
+function has been contributed by Powerdog Industries.
+.Pp
+This man page was written by
+.An J\(:org Wunsch .
+.Sh CAVEATS
+The
+.Fn strptime
+function assumes the Gregorian calendar and will produce incorrect
+results for dates prior to its introduction.
+.Sh BUGS
+Both the
+.Fa %e
+and
+.Fa %l
+format specifiers may incorrectly scan one too many digits
+if the intended values comprise only a single digit
+and that digit is followed immediately by another digit.
+Both specifiers accept zero-padded values,
+even though they are both defined as taking unpadded values.
+.Pp
+The
+.Fa %p
+format specifier has no effect unless it is parsed
+.Em after
+hour-related specifiers.
+Specifying
+.Fa %l
+without
+.Fa %p
+will produce undefined results.
+Note that 12AM
+(ante meridiem)
+is taken as midnight
+and 12PM
+(post meridiem)
+is taken as noon.
+.Pp
+The
+.Fa %Z
+format specifier only accepts time zone abbreviations of the local time zone,
+or the value "GMT".
+This limitation is because of ambiguity due to of the over loading of time
+zone abbreviations.
+One such example is
+.Fa EST
+which is both Eastern Standard Time and Eastern Australia Summer Time.
+.Pp
+The
+.Fn strptime
+function does not correctly handle multibyte characters in the
+.Fa format
+argument.
diff --git a/lib/libc/stdtime/strptime.c b/lib/libc/stdtime/strptime.c
new file mode 100644
index 000000000000..5f1293c7a267
--- /dev/null
+++ b/lib/libc/stdtime/strptime.c
@@ -0,0 +1,709 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2014 Gary Mills
+ * Copyright 2011, Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 1994 Powerdog Industries. All rights reserved.
+ *
+ * Copyright (c) 2011 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by David Chisnall
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY POWERDOG INDUSTRIES ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE POWERDOG INDUSTRIES BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of Powerdog Industries.
+ */
+
+#include "namespace.h"
+#include <time.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include "private.h"
+#include "un-namespace.h"
+#include "libc_private.h"
+#include "timelocal.h"
+#include "tzfile.h"
+
+static char * _strptime(const char *, const char *, struct tm *, int *, locale_t);
+
+#define asizeof(a) (sizeof(a) / sizeof((a)[0]))
+
+#define FLAG_NONE (1 << 0)
+#define FLAG_YEAR (1 << 1)
+#define FLAG_MONTH (1 << 2)
+#define FLAG_YDAY (1 << 3)
+#define FLAG_MDAY (1 << 4)
+#define FLAG_WDAY (1 << 5)
+
+/*
+ * Gauss's algorithm for the day of the week of the first day of any year
+ * in the Gregorian calendar.
+ */
+static int
+first_wday_of(int year)
+{
+ return ((1 +
+ 5 * ((year - 1) % 4) +
+ 4 * ((year - 1) % 100) +
+ 6 * ((year - 1) % 400)) % 7);
+}
+
+static char *
+_strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp,
+ locale_t locale)
+{
+ char c;
+ const char *ptr;
+ int day_offset = -1, wday_offset;
+ int week_offset;
+ int i, len;
+ int flags;
+ int Ealternative, Oalternative;
+ int century, year;
+ const struct lc_time_T *tptr = __get_current_time_locale(locale);
+ static int start_of_month[2][13] = {
+ {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
+ {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
+ };
+
+ flags = FLAG_NONE;
+ century = -1;
+ year = -1;
+
+ ptr = fmt;
+ while (*ptr != 0) {
+ c = *ptr++;
+
+ if (c != '%') {
+ if (isspace_l((unsigned char)c, locale))
+ while (*buf != 0 &&
+ isspace_l((unsigned char)*buf, locale))
+ buf++;
+ else if (c != *buf++)
+ return (NULL);
+ continue;
+ }
+
+ Ealternative = 0;
+ Oalternative = 0;
+label:
+ c = *ptr++;
+ switch (c) {
+ case '%':
+ if (*buf++ != '%')
+ return (NULL);
+ break;
+
+ case '+':
+ buf = _strptime(buf, tptr->date_fmt, tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ flags |= FLAG_WDAY | FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
+ break;
+
+ case 'C':
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ /* XXX This will break for 3-digit centuries. */
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+
+ century = i;
+ flags |= FLAG_YEAR;
+
+ break;
+
+ case 'c':
+ buf = _strptime(buf, tptr->c_fmt, tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ flags |= FLAG_WDAY | FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
+ break;
+
+ case 'D':
+ buf = _strptime(buf, "%m/%d/%y", tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
+ break;
+
+ case 'E':
+ if (Ealternative || Oalternative)
+ break;
+ Ealternative++;
+ goto label;
+
+ case 'O':
+ if (Ealternative || Oalternative)
+ break;
+ Oalternative++;
+ goto label;
+
+ case 'F':
+ buf = _strptime(buf, "%Y-%m-%d", tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
+ break;
+
+ case 'R':
+ buf = _strptime(buf, "%H:%M", tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ break;
+
+ case 'r':
+ buf = _strptime(buf, tptr->ampm_fmt, tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ break;
+
+ case 'T':
+ buf = _strptime(buf, "%H:%M:%S", tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ break;
+
+ case 'X':
+ buf = _strptime(buf, tptr->X_fmt, tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ break;
+
+ case 'x':
+ buf = _strptime(buf, tptr->x_fmt, tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
+ break;
+
+ case 'j':
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 3;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++){
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (i < 1 || i > 366)
+ return (NULL);
+
+ tm->tm_yday = i - 1;
+ flags |= FLAG_YDAY;
+
+ break;
+
+ case 'M':
+ case 'S':
+ if (*buf == 0 ||
+ isspace_l((unsigned char)*buf, locale))
+ break;
+
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++){
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+
+ if (c == 'M') {
+ if (i > 59)
+ return (NULL);
+ tm->tm_min = i;
+ } else {
+ if (i > 60)
+ return (NULL);
+ tm->tm_sec = i;
+ }
+
+ break;
+
+ case 'H':
+ case 'I':
+ case 'k':
+ case 'l':
+ /*
+ * %k and %l specifiers are documented as being
+ * blank-padded. However, there is no harm in
+ * allowing zero-padding.
+ *
+ * XXX %k and %l specifiers may gobble one too many
+ * digits if used incorrectly.
+ */
+
+ len = 2;
+ if ((c == 'k' || c == 'l') &&
+ isblank_l((unsigned char)*buf, locale)) {
+ buf++;
+ len = 1;
+ }
+
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (c == 'H' || c == 'k') {
+ if (i > 23)
+ return (NULL);
+ } else if (i == 0 || i > 12)
+ return (NULL);
+
+ tm->tm_hour = i;
+
+ break;
+
+ case 'p':
+ /*
+ * XXX This is bogus if parsed before hour-related
+ * specifiers.
+ */
+ if (tm->tm_hour > 12)
+ return (NULL);
+
+ len = strlen(tptr->am);
+ if (strncasecmp_l(buf, tptr->am, len, locale) == 0) {
+ if (tm->tm_hour == 12)
+ tm->tm_hour = 0;
+ buf += len;
+ break;
+ }
+
+ len = strlen(tptr->pm);
+ if (strncasecmp_l(buf, tptr->pm, len, locale) == 0) {
+ if (tm->tm_hour != 12)
+ tm->tm_hour += 12;
+ buf += len;
+ break;
+ }
+
+ return (NULL);
+
+ case 'A':
+ case 'a':
+ for (i = 0; i < asizeof(tptr->weekday); i++) {
+ len = strlen(tptr->weekday[i]);
+ if (strncasecmp_l(buf, tptr->weekday[i],
+ len, locale) == 0)
+ break;
+ len = strlen(tptr->wday[i]);
+ if (strncasecmp_l(buf, tptr->wday[i],
+ len, locale) == 0)
+ break;
+ }
+ if (i == asizeof(tptr->weekday))
+ return (NULL);
+
+ buf += len;
+ tm->tm_wday = i;
+ flags |= FLAG_WDAY;
+ break;
+
+ case 'U':
+ case 'W':
+ /*
+ * XXX This is bogus, as we can not assume any valid
+ * information present in the tm structure at this
+ * point to calculate a real value, so just check the
+ * range for now.
+ */
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (i > 53)
+ return (NULL);
+
+ if (c == 'U')
+ day_offset = TM_SUNDAY;
+ else
+ day_offset = TM_MONDAY;
+
+
+ week_offset = i;
+
+ break;
+
+ case 'u':
+ case 'w':
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ i = *buf++ - '0';
+ if (i < 0 || i > 7 || (c == 'u' && i < 1) ||
+ (c == 'w' && i > 6))
+ return (NULL);
+
+ tm->tm_wday = i % 7;
+ flags |= FLAG_WDAY;
+
+ break;
+
+ case 'e':
+ /*
+ * With %e format, our strftime(3) adds a blank space
+ * before single digits.
+ */
+ if (*buf != 0 &&
+ isspace_l((unsigned char)*buf, locale))
+ buf++;
+ /* FALLTHROUGH */
+ case 'd':
+ /*
+ * The %e specifier was once explicitly documented as
+ * not being zero-padded but was later changed to
+ * equivalent to %d. There is no harm in allowing
+ * such padding.
+ *
+ * XXX The %e specifier may gobble one too many
+ * digits if used incorrectly.
+ */
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (i == 0 || i > 31)
+ return (NULL);
+
+ tm->tm_mday = i;
+ flags |= FLAG_MDAY;
+
+ break;
+
+ case 'B':
+ case 'b':
+ case 'h':
+ for (i = 0; i < asizeof(tptr->month); i++) {
+ if (Oalternative) {
+ if (c == 'B') {
+ len = strlen(tptr->alt_month[i]);
+ if (strncasecmp_l(buf,
+ tptr->alt_month[i],
+ len, locale) == 0)
+ break;
+ }
+ } else {
+ len = strlen(tptr->month[i]);
+ if (strncasecmp_l(buf, tptr->month[i],
+ len, locale) == 0)
+ break;
+ }
+ }
+ /*
+ * Try the abbreviated month name if the full name
+ * wasn't found and Oalternative was not requested.
+ */
+ if (i == asizeof(tptr->month) && !Oalternative) {
+ for (i = 0; i < asizeof(tptr->month); i++) {
+ len = strlen(tptr->mon[i]);
+ if (strncasecmp_l(buf, tptr->mon[i],
+ len, locale) == 0)
+ break;
+ }
+ }
+ if (i == asizeof(tptr->month))
+ return (NULL);
+
+ tm->tm_mon = i;
+ buf += len;
+ flags |= FLAG_MONTH;
+
+ break;
+
+ case 'm':
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (i < 1 || i > 12)
+ return (NULL);
+
+ tm->tm_mon = i - 1;
+ flags |= FLAG_MONTH;
+
+ break;
+
+ case 's':
+ {
+ char *cp;
+ int sverrno;
+ long n;
+ time_t t;
+
+ sverrno = errno;
+ errno = 0;
+ n = strtol_l(buf, &cp, 10, locale);
+ if (errno == ERANGE || (long)(t = n) != n) {
+ errno = sverrno;
+ return (NULL);
+ }
+ errno = sverrno;
+ buf = cp;
+ if (gmtime_r(&t, tm) == NULL)
+ return (NULL);
+ *GMTp = 1;
+ flags |= FLAG_YDAY | FLAG_WDAY | FLAG_MONTH |
+ FLAG_MDAY | FLAG_YEAR;
+ }
+ break;
+
+ case 'Y':
+ case 'y':
+ if (*buf == 0 ||
+ isspace_l((unsigned char)*buf, locale))
+ break;
+
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = (c == 'Y') ? 4 : 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (c == 'Y')
+ century = i / 100;
+ year = i % 100;
+
+ flags |= FLAG_YEAR;
+
+ break;
+
+ case 'Z':
+ {
+ const char *cp;
+ char *zonestr;
+
+ for (cp = buf; *cp &&
+ isupper_l((unsigned char)*cp, locale); ++cp) {
+ /*empty*/}
+ if (cp - buf) {
+ zonestr = alloca(cp - buf + 1);
+ strncpy(zonestr, buf, cp - buf);
+ zonestr[cp - buf] = '\0';
+ tzset();
+ if (0 == strcmp(zonestr, "GMT") ||
+ 0 == strcmp(zonestr, "UTC")) {
+ *GMTp = 1;
+ } else if (0 == strcmp(zonestr, tzname[0])) {
+ tm->tm_isdst = 0;
+ } else if (0 == strcmp(zonestr, tzname[1])) {
+ tm->tm_isdst = 1;
+ } else {
+ return (NULL);
+ }
+ buf += cp - buf;
+ }
+ }
+ break;
+
+ case 'z':
+ {
+ int sign = 1;
+
+ if (*buf != '+') {
+ if (*buf == '-')
+ sign = -1;
+ else
+ return (NULL);
+ }
+
+ buf++;
+ i = 0;
+ for (len = 4; len > 0; len--) {
+ if (isdigit_l((unsigned char)*buf, locale)) {
+ i *= 10;
+ i += *buf - '0';
+ buf++;
+ } else if (len == 2) {
+ i *= 100;
+ break;
+ } else
+ return (NULL);
+ }
+
+ if (i > 1400 || (sign == -1 && i > 1200) ||
+ (i % 100) >= 60)
+ return (NULL);
+ tm->tm_hour -= sign * (i / 100);
+ tm->tm_min -= sign * (i % 100);
+ *GMTp = 1;
+ }
+ break;
+
+ case 'n':
+ case 't':
+ while (isspace_l((unsigned char)*buf, locale))
+ buf++;
+ break;
+
+ default:
+ return (NULL);
+ }
+ }
+
+ if (century != -1 || year != -1) {
+ if (year == -1)
+ year = 0;
+ if (century == -1) {
+ if (year < 69)
+ year += 100;
+ } else
+ year += century * 100 - TM_YEAR_BASE;
+ tm->tm_year = year;
+ }
+
+ if (!(flags & FLAG_YDAY) && (flags & FLAG_YEAR)) {
+ if ((flags & (FLAG_MONTH | FLAG_MDAY)) ==
+ (FLAG_MONTH | FLAG_MDAY)) {
+ tm->tm_yday = start_of_month[isleap(tm->tm_year +
+ TM_YEAR_BASE)][tm->tm_mon] + (tm->tm_mday - 1);
+ flags |= FLAG_YDAY;
+ } else if (day_offset != -1) {
+ int tmpwday, tmpyday, fwo;
+
+ fwo = first_wday_of(tm->tm_year + TM_YEAR_BASE);
+ /* No incomplete week (week 0). */
+ if (week_offset == 0 && fwo == day_offset)
+ return (NULL);
+
+ /* Set the date to the first Sunday (or Monday)
+ * of the specified week of the year.
+ */
+ tmpwday = (flags & FLAG_WDAY) ? tm->tm_wday :
+ day_offset;
+ tmpyday = (7 - fwo + day_offset) % 7 +
+ (week_offset - 1) * 7 +
+ (tmpwday - day_offset + 7) % 7;
+ /* Impossible yday for incomplete week (week 0). */
+ if (tmpyday < 0) {
+ if (flags & FLAG_WDAY)
+ return (NULL);
+ tmpyday = 0;
+ }
+ tm->tm_yday = tmpyday;
+ flags |= FLAG_YDAY;
+ }
+ }
+
+ if ((flags & (FLAG_YEAR | FLAG_YDAY)) == (FLAG_YEAR | FLAG_YDAY)) {
+ if (!(flags & FLAG_MONTH)) {
+ i = 0;
+ while (tm->tm_yday >=
+ start_of_month[isleap(tm->tm_year +
+ TM_YEAR_BASE)][i])
+ i++;
+ if (i > 12) {
+ i = 1;
+ tm->tm_yday -=
+ start_of_month[isleap(tm->tm_year +
+ TM_YEAR_BASE)][12];
+ tm->tm_year++;
+ }
+ tm->tm_mon = i - 1;
+ flags |= FLAG_MONTH;
+ }
+ if (!(flags & FLAG_MDAY)) {
+ tm->tm_mday = tm->tm_yday -
+ start_of_month[isleap(tm->tm_year + TM_YEAR_BASE)]
+ [tm->tm_mon] + 1;
+ flags |= FLAG_MDAY;
+ }
+ if (!(flags & FLAG_WDAY)) {
+ wday_offset = first_wday_of(tm->tm_year + TM_YEAR_BASE);
+ tm->tm_wday = (wday_offset + tm->tm_yday) % 7;
+ flags |= FLAG_WDAY;
+ }
+ }
+
+ return ((char *)buf);
+}
+
+char *
+strptime_l(const char * __restrict buf, const char * __restrict fmt,
+ struct tm * __restrict tm, locale_t loc)
+{
+ char *ret;
+ int gmt;
+ FIX_LOCALE(loc);
+
+ gmt = 0;
+ ret = _strptime(buf, fmt, tm, &gmt, loc);
+ if (ret && gmt) {
+ time_t t = timegm(tm);
+
+ localtime_r(&t, tm);
+ }
+
+ return (ret);
+}
+
+char *
+strptime(const char * __restrict buf, const char * __restrict fmt,
+ struct tm * __restrict tm)
+{
+ return strptime_l(buf, fmt, tm, __get_locale());
+}
diff --git a/lib/libc/stdtime/time32.c b/lib/libc/stdtime/time32.c
new file mode 100644
index 000000000000..5fb7c95c22de
--- /dev/null
+++ b/lib/libc/stdtime/time32.c
@@ -0,0 +1,97 @@
+/*-
+ * Copyright (c) 2001 FreeBSD Inc.
+ * All rights reserved.
+ *
+ * These routines are for converting time_t to fixed-bit representations
+ * for use in protocols or storage. When converting time to a larger
+ * representation of time_t these routines are expected to assume temporal
+ * locality and use the 50-year rule to properly set the msb bits. XXX
+ *
+ * Redistribution and use under the terms of the COPYRIGHT file at the
+ * base of the source tree.
+ */
+
+#include <sys/types.h>
+#include <timeconv.h>
+
+/*
+ * Convert a 32 bit representation of time_t into time_t. XXX needs to
+ * implement the 50-year rule to handle post-2038 conversions.
+ */
+time_t
+_time32_to_time(__int32_t t32)
+{
+ return((time_t)t32);
+}
+
+/*
+ * Convert time_t to a 32 bit representation. If time_t is 64 bits we can
+ * simply chop it down. The resulting 32 bit representation can be
+ * converted back to a temporally local 64 bit time_t using time32_to_time.
+ */
+__int32_t
+_time_to_time32(time_t t)
+{
+ return((__int32_t)t);
+}
+
+/*
+ * Convert a 64 bit representation of time_t into time_t. If time_t is
+ * represented as 32 bits we can simply chop it and not support times
+ * past 2038.
+ */
+time_t
+_time64_to_time(__int64_t t64)
+{
+ return((time_t)t64);
+}
+
+/*
+ * Convert time_t to a 64 bit representation. If time_t is represented
+ * as 32 bits we simply sign-extend and do not support times past 2038.
+ */
+__int64_t
+_time_to_time64(time_t t)
+{
+ return((__int64_t)t);
+}
+
+/*
+ * Convert to/from 'long'. Depending on the sizeof(long) this may or
+ * may not require using the 50-year rule.
+ */
+long
+_time_to_long(time_t t)
+{
+ if (sizeof(long) == sizeof(__int64_t))
+ return(_time_to_time64(t));
+ return((long)t);
+}
+
+time_t
+_long_to_time(long tlong)
+{
+ if (sizeof(long) == sizeof(__int32_t))
+ return(_time32_to_time(tlong));
+ return((time_t)tlong);
+}
+
+/*
+ * Convert to/from 'int'. Depending on the sizeof(int) this may or
+ * may not require using the 50-year rule.
+ */
+int
+_time_to_int(time_t t)
+{
+ if (sizeof(int) == sizeof(__int64_t))
+ return(_time_to_time64(t));
+ return((int)t);
+}
+
+time_t
+_int_to_time(int tint)
+{
+ if (sizeof(int) == sizeof(__int32_t))
+ return(_time32_to_time(tint));
+ return((time_t)tint);
+}
diff --git a/lib/libc/stdtime/timelocal.c b/lib/libc/stdtime/timelocal.c
new file mode 100644
index 000000000000..680270e0f9b1
--- /dev/null
+++ b/lib/libc/stdtime/timelocal.c
@@ -0,0 +1,153 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2001 Alexey Zelkin <phantom@FreeBSD.org>
+ * Copyright (c) 1997 FreeBSD Inc.
+ * All rights reserved.
+ *
+ * Copyright (c) 2011 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by David Chisnall
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stddef.h>
+
+#include "ldpart.h"
+#include "timelocal.h"
+
+struct xlocale_time {
+ struct xlocale_component header;
+ char *buffer;
+ struct lc_time_T locale;
+};
+
+struct xlocale_time __xlocale_global_time;
+
+#define LCTIME_SIZE (sizeof(struct lc_time_T) / sizeof(char *))
+
+static const struct lc_time_T _C_time_locale = {
+ {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ }, {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+ }, {
+ "Sun", "Mon", "Tue", "Wed",
+ "Thu", "Fri", "Sat"
+ }, {
+ "Sunday", "Monday", "Tuesday", "Wednesday",
+ "Thursday", "Friday", "Saturday"
+ },
+
+ /* X_fmt */
+ "%H:%M:%S",
+
+ /*
+ * x_fmt
+ * Since the C language standard calls for
+ * "date, using locale's date format," anything goes.
+ * Using just numbers (as here) makes Quakers happier;
+ * it's also compatible with SVR4.
+ */
+ "%m/%d/%y",
+
+ /*
+ * c_fmt
+ */
+ "%a %b %e %H:%M:%S %Y",
+
+ /* am */
+ "AM",
+
+ /* pm */
+ "PM",
+
+ /* date_fmt */
+ "%a %b %e %H:%M:%S %Z %Y",
+
+ /* alt_month
+ * Standalone months forms for %OB
+ */
+ {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+ },
+
+ /* md_order
+ * Month / day order in dates
+ */
+ "md",
+
+ /* ampm_fmt
+ * To determine 12-hour clock format time (empty, if N/A)
+ */
+ "%I:%M:%S %p"
+};
+
+static void destruct_time(void *v)
+{
+ struct xlocale_time *l = v;
+ if (l->buffer)
+ free(l->buffer);
+ free(l);
+}
+
+#include <stdio.h>
+struct lc_time_T *
+__get_current_time_locale(locale_t loc)
+{
+ return (loc->using_time_locale
+ ? &((struct xlocale_time *)loc->components[XLC_TIME])->locale
+ : (struct lc_time_T *)&_C_time_locale);
+}
+
+static int
+time_load_locale(struct xlocale_time *l, int *using_locale, const char *name)
+{
+ struct lc_time_T *time_locale = &l->locale;
+ return (__part_load_locale(name, using_locale,
+ &l->buffer, "LC_TIME",
+ LCTIME_SIZE, LCTIME_SIZE,
+ (const char **)time_locale));
+}
+int
+__time_load_locale(const char *name)
+{
+ return time_load_locale(&__xlocale_global_time,
+ &__xlocale_global_locale.using_time_locale, name);
+}
+void* __time_load(const char* name, locale_t loc)
+{
+ struct xlocale_time *new = calloc(sizeof(struct xlocale_time), 1);
+ new->header.header.destructor = destruct_time;
+ if (time_load_locale(new, &loc->using_time_locale, name) == _LDP_ERROR)
+ {
+ xlocale_release(new);
+ return NULL;
+ }
+ return new;
+}
+
diff --git a/lib/libc/stdtime/timelocal.h b/lib/libc/stdtime/timelocal.h
new file mode 100644
index 000000000000..7fe62b41ebfd
--- /dev/null
+++ b/lib/libc/stdtime/timelocal.h
@@ -0,0 +1,61 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 1997-2002 FreeBSD Project.
+ * All rights reserved.
+ *
+ * Copyright (c) 2011 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by David Chisnall
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _TIMELOCAL_H_
+#define _TIMELOCAL_H_
+#include "xlocale_private.h"
+
+/*
+ * Private header file for the strftime and strptime localization
+ * stuff.
+ */
+struct lc_time_T {
+ const char *mon[12];
+ const char *month[12];
+ const char *wday[7];
+ const char *weekday[7];
+ const char *X_fmt;
+ const char *x_fmt;
+ const char *c_fmt;
+ const char *am;
+ const char *pm;
+ const char *date_fmt;
+ const char *alt_month[12];
+ const char *md_order;
+ const char *ampm_fmt;
+};
+
+struct lc_time_T *__get_current_time_locale(locale_t);
+int __time_load_locale(const char *);
+
+#endif /* !_TIMELOCAL_H_ */
diff --git a/lib/libc/stdtime/tzset.3 b/lib/libc/stdtime/tzset.3
new file mode 100644
index 000000000000..94ccbec9aba7
--- /dev/null
+++ b/lib/libc/stdtime/tzset.3
@@ -0,0 +1,344 @@
+.\" Copyright (c) 1989, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Arthur Olson.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd December 25, 2023
+.Dt TZSET 3
+.Os
+.Sh NAME
+.Nm daylight ,
+.Nm timezone ,
+.Nm tzname ,
+.Nm tzset
+.Nd initialize time conversion information
+.Sh LIBRARY
+.Lb libc
+.Sh SYNOPSIS
+.In time.h
+.Vt extern int daylight ;
+.Vt extern long timezone ;
+.Vt extern char *tzname[2] ;
+.Ft void
+.Fn tzset void
+.Sh DESCRIPTION
+The
+.Fn tzset
+function
+initializes time conversion information used by the library routine
+.Xr localtime 3 .
+The environment variable
+.Ev TZ
+specifies how this is done.
+.Pp
+If
+.Ev TZ
+does not appear in the environment, the best available approximation to
+local wall clock time, as specified by the
+.Xr tzfile 5 Ns -format
+file
+.Pa /etc/localtime
+is used.
+.Pp
+If
+.Ev TZ
+appears in the environment but its value is a null string, Coordinated
+Universal Time
+.Pq Tn UTC
+is used (without leap second correction).
+.Pp
+If
+.Ev TZ
+appears in the environment and its value begins with a colon
+.Pq Ql \&: ,
+the rest of its value is used as a pathname of a
+.Xr tzfile 5 Ns -format
+file from which to read the time conversion information.
+If the first character of the pathname is a slash
+.Pq Ql /
+it is used as
+an absolute pathname; otherwise, it is used as a pathname relative to
+the system time conversion information directory.
+.Pp
+If its value does not begin with a colon, it is first used as the pathname
+of a file (as described above) from which to read the time conversion
+information.
+If that file cannot be read, the value is then interpreted as a direct
+specification (the format is described below) of the time conversion
+information.
+.Pp
+If the
+.Ev TZ
+environment variable does not specify a
+.Xr tzfile 5 Ns -format
+file and cannot be interpreted as a direct specification,
+.Tn UTC
+is used.
+.Pp
+After the first call to
+.Nm tzset ,
+the
+.Vt timezone
+variable is set to the difference, in seconds, between Coordinated Universal
+Time (UTC) and the local time.
+The
+.Vt daylight
+variable is set to 0 if the local timezone is observing standard time.
+It is set to 1 if the local timezone ever observes an alternate time, such as summer time.
+The first element of the
+.Vt tzname
+array is the timezone name of standard time, while the second element is the
+name of the altnerative time zone.
+.Sh SPECIFICATION FORMAT
+When
+.Ev TZ
+is used directly as a specification of the time conversion information,
+it must have the following syntax (spaces inserted for clarity):
+.Bd -ragged -offset indent
+.Em std offset
+.Bo
+.Em dst
+.Bq Em offset
+.Bq , Em rule
+.Bc
+.Ed
+.Pp
+Where:
+.Bl -tag -width std_and_dst -offset indent
+.It Em std No and Em dst
+Three or more bytes that are the designation for the standard
+.Pq Em std
+or summer
+.Pq Em dst
+time zone.
+Only
+.Em std
+is required; if
+.Em dst
+is missing, then summer time does not apply in this locale.
+Upper and lowercase letters are explicitly allowed.
+Any characters
+except a leading colon
+.Pq Ql \&: ,
+digits, comma
+.Pq Ql \&, ,
+minus
+.Pq Ql \- ,
+plus
+.Pq Ql + ,
+and
+.Tn ASCII
+.Dv NUL
+are allowed.
+.It Em offset
+Indicates the value one must add to the local time to arrive at
+Coordinated Universal Time.
+The
+.Em offset
+has the form:
+.Bd -ragged -offset indent
+.Sm off
+.Em hh Bo
+.Em : mm
+.Bq Em : ss
+.Bc
+.Sm on
+.Ed
+.Pp
+The minutes
+.Pq Em mm
+and seconds
+.Pq Em ss
+are optional.
+The hour
+.Pq Em hh
+is required and may be a single digit.
+The
+.Em offset
+following
+.Em std
+is required.
+If no
+.Em offset
+follows
+.Em dst ,
+summer time is assumed to be one hour ahead of standard time.
+One or
+more digits may be used; the value is always interpreted as a decimal
+number.
+The hour must be between zero and 24, and the minutes (and
+seconds) \(em if present \(em between zero and 59.
+If preceded by a
+.Pq Ql \-
+the time zone shall be east of the Prime Meridian; otherwise it shall be
+west (which may be indicated by an optional preceding
+.Pq Ql + ) .
+.It Em rule
+Indicates when to change to and back from summer time.
+The
+.Em rule
+has the form:
+.Bd -ragged -offset indent
+.Em date/time,date/time
+.Ed
+.Pp
+where the first
+.Em date
+describes when the change from standard to summer time occurs and the
+second
+.Em date
+describes when the change back happens.
+Each
+.Em time
+field describes when, in current local time, the change to the other
+time is made.
+.Pp
+The format of
+.Em date
+is one of the following:
+.Bl -tag -width "M.m.n.d"
+.It Sy J Em n
+The Julian day
+.Em n
+(1 \*(Le
+.Em n
+\*(Le 365).
+Leap days are not counted; that is, in all years \(em including leap
+years \(em February 28 is day 59 and March 1 is day 60.
+It is
+impossible to explicitly refer to the occasional February 29.
+.It Em n
+The zero-based Julian day
+(0 \*(Le
+.Em n
+\*(Le 365 ) .
+Leap days are counted, and it is possible to refer to February 29.
+.It Sy M Em m.n.d
+The
+.Em d Ns 'th
+day (0 \*(Le
+.Em d
+\*(Le 6)
+of week
+.Em n
+of month
+.Em m
+of the year
+(1 \*(Le
+.Em n
+\*(Le 5),
+(1 \*(Le
+.Em m
+\*(Le 12),
+where week 5 means
+.Do
+the last
+.Em d
+day in month
+.Em m
+.Dc
+which may occur in either the fourth or the fifth week).
+Week 1 is the
+first week in which the
+.Em d Ns 'th
+day occurs.
+Day zero is Sunday.
+.Pp
+The
+.Em time
+has the same format as
+.Em offset
+except that no leading sign
+.Pq Ql \-
+or
+.Pq Ql +
+is allowed.
+The default, if
+.Em time
+is not given, is
+.Sy 02:00:00 .
+.El
+.Pp
+If no
+.Em rule
+is present in the
+.Ev TZ
+specification, the rules specified
+by the
+.Xr tzfile 5 Ns -format
+file
+.Em posixrules
+in the system time conversion information directory are used, with the
+standard and summer time offsets from
+.Tn UTC
+replaced by those specified by
+the
+.Em offset
+values in
+.Ev TZ .
+.El
+.Pp
+For compatibility with System V Release 3.1, a semicolon
+.Pq Ql \&;
+may be used to separate the
+.Em rule
+from the rest of the specification.
+.Sh FILES
+.Bl -tag -width /usr/share/zoneinfo/posixrules -compact
+.It Pa /etc/localtime
+local time zone file
+.It Pa /usr/share/zoneinfo
+time zone directory
+.It Pa /usr/share/zoneinfo/posixrules
+rules for
+.Tn POSIX Ns -style
+.Tn TZ Ns 's
+.It Pa /usr/share/zoneinfo/Etc/GMT
+for
+.Tn UTC
+leap seconds
+.El
+.Pp
+If the file
+.Pa /usr/share/zoneinfo/UTC
+does not exist,
+.Tn UTC
+leap seconds are loaded from
+.Pa /usr/share/zoneinfo/posixrules .
+.Sh SEE ALSO
+.Xr date 1 ,
+.Xr gettimeofday 2 ,
+.Xr ctime 3 ,
+.Xr getenv 3 ,
+.Xr time 3 ,
+.Xr tzfile 5
+.Sh HISTORY
+The
+.Fn tzset
+function first appeared in
+.Bx 4.4 .