Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[libc] Implement strftime #122556

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libc/config/linux/x86_64/entrypoints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.time.gmtime_r
libc.src.time.mktime
libc.src.time.nanosleep
libc.src.time.strftime
libc.src.time.time
libc.src.time.timespec_get

Expand Down
24 changes: 24 additions & 0 deletions libc/docs/dev/undefined_behavior.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,27 @@ uninitialized spinlock and invalid spinlock is left undefined. We follow the rec
POSIX.1-2024, where EINVAL is returned if the spinlock is invalid (here we only check for null pointers) or
EBUSY is returned if the spinlock is currently locked. The lock is poisoned after a successful destroy. That is,
subsequent operations on the lock object without any reinitialization will return EINVAL.

Strftime
--------
In the C Standard, it provides a list of modifiers, and the conversions these
are valid on. It also says that a modifier on an unspecified conversion is
undefined. For LLVM-libc, the conversion is treated as if the modifier isn't
there.

If a struct tm with values out of the normal range is passed, the standard says
the result is undefined. For LLVM-libc, the result may be either the normalized
value (e.g. weekday % 7) or the actual, out of range value. This behavior is not
necessarily consistent between conversions, even similar ones. For conversions
that result in strings, passing an out of range value will result in "?".

Posix adds padding support to strftime, but says "the default padding character
is unspecified." For LLVM-libc, the default padding character is ' ' (space),
for all string-type conversions and '0' for integer-type conversions.

Posix also adds flags and a minimum field width, but leaves unspecified what
happens for most combinations of these. For LLVM-libc:
An unspecified minimum field width defaults to 0.
More specific flags take precedence over less specific flags (i.e. '+' takes precedence over '0')
Any conversion with a minimum width is padded with the padding character until it is at least as long as the minimum width.
Modifiers are applied, then the result is padded if necessary.
1 change: 1 addition & 0 deletions libc/include/llvm-libc-types/struct_tm.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct tm {
int tm_wday; // days since Sunday
int tm_yday; // days since January
int tm_isdst; // Daylight Saving Time flag
// TODO: add tm_gmtoff and tm_zone? (posix extensions)
};

#endif // LLVM_LIBC_TYPES_STRUCT_TM_H
19 changes: 19 additions & 0 deletions libc/include/time.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,25 @@ functions:
arguments:
- type: const struct timespec *
- type: struct timespec *
- name: strftime
standard:
- stdc
return_type: size_t
arguments:
- type: char *__restrict
- type: size_t
- type: const char *__restrict
- type: const struct tm *__restrict
- name: strftime_l
standard:
- stdc
return_type: size_t
arguments:
- type: char *__restrict
- type: size_t
- type: const char *__restrict
- type: const struct tm *__restrict
- type: locale_t
- name: time
standard:
- stdc
Expand Down
17 changes: 17 additions & 0 deletions libc/src/time/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ add_object_library(
DEPENDS
libc.include.time
libc.src.__support.CPP.limits
libc.src.__support.CPP.string_view
libc.src.__support.CPP.optional
libc.src.errno.errno
.time_constants
libc.hdr.types.time_t
Expand Down Expand Up @@ -133,6 +135,21 @@ add_entrypoint_object(
libc.hdr.types.struct_tm
)

add_subdirectory(strftime_core) #TODO: Move to top

add_entrypoint_object(
strftime
SRCS
strftime.cpp
HDRS
strftime.h
DEPENDS
libc.hdr.types.size_t
libc.hdr.types.struct_tm
libc.src.stdio.printf_core.writer
libc.src.time.strftime_core.strftime_main
)

add_entrypoint_object(
time
SRCS
Expand Down
32 changes: 13 additions & 19 deletions libc/src/time/mktime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,6 @@

namespace LIBC_NAMESPACE_DECL {

// Returns number of years from (1, year).
static constexpr int64_t get_num_of_leap_years_before(int64_t year) {
return (year / 4) - (year / 100) + (year / 400);
}

// Returns True if year is a leap year.
static constexpr bool is_leap_year(const int64_t year) {
return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0));
}

LLVM_LIBC_FUNCTION(time_t, mktime, (struct tm * tm_out)) {
// Unlike most C Library functions, mktime doesn't just die on bad input.
// TODO(rtenneti); Handle leap seconds.
Expand Down Expand Up @@ -69,7 +59,7 @@ LLVM_LIBC_FUNCTION(time_t, mktime, (struct tm * tm_out)) {
}
tm_year_from_base += years;
}
bool tm_year_is_leap = is_leap_year(tm_year_from_base);
bool tm_year_is_leap = time_utils::is_leap_year(tm_year_from_base);

// Calculate total number of days based on the month and the day (tm_mday).
int64_t total_days = tm_out->tm_mday - 1;
Expand All @@ -83,21 +73,25 @@ LLVM_LIBC_FUNCTION(time_t, mktime, (struct tm * tm_out)) {
total_days += (tm_year_from_base - time_constants::EPOCH_YEAR) *
time_constants::DAYS_PER_NON_LEAP_YEAR;
if (tm_year_from_base >= time_constants::EPOCH_YEAR) {
total_days += get_num_of_leap_years_before(tm_year_from_base - 1) -
get_num_of_leap_years_before(time_constants::EPOCH_YEAR);
total_days +=
time_utils::get_num_of_leap_years_before(tm_year_from_base - 1) -
time_utils::get_num_of_leap_years_before(time_constants::EPOCH_YEAR);
} else if (tm_year_from_base >= 1) {
total_days -= get_num_of_leap_years_before(time_constants::EPOCH_YEAR) -
get_num_of_leap_years_before(tm_year_from_base - 1);
total_days -=
time_utils::get_num_of_leap_years_before(time_constants::EPOCH_YEAR) -
time_utils::get_num_of_leap_years_before(tm_year_from_base - 1);
} else {
// Calculate number of leap years until 0th year.
total_days -= get_num_of_leap_years_before(time_constants::EPOCH_YEAR) -
get_num_of_leap_years_before(0);
total_days -=
time_utils::get_num_of_leap_years_before(time_constants::EPOCH_YEAR) -
time_utils::get_num_of_leap_years_before(0);
if (tm_year_from_base <= 0) {
total_days -= 1; // Subtract 1 for 0th year.
// Calculate number of leap years until -1 year
if (tm_year_from_base < 0) {
total_days -= get_num_of_leap_years_before(-tm_year_from_base) -
get_num_of_leap_years_before(1);
total_days -=
time_utils::get_num_of_leap_years_before(-tm_year_from_base) -
time_utils::get_num_of_leap_years_before(1);
}
}
}
Expand Down
31 changes: 31 additions & 0 deletions libc/src/time/strftime.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//===-- Implementation of strftime function -------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/time/strftime.h"
#include "hdr/types/size_t.h"
#include "hdr/types/struct_tm.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/stdio/printf_core/writer.h"
#include "src/time/strftime_core/strftime_main.h"

namespace LIBC_NAMESPACE_DECL {

LLVM_LIBC_FUNCTION(size_t, strftime,
(char *__restrict buffer, size_t buffsz,
const char *__restrict format, const struct tm *timeptr)) {

printf_core::WriteBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
printf_core::Writer writer(&wb);
int ret = strftime_core::strftime_main(&writer, format, timeptr);
if (buffsz > 0) // if the buffsz is 0 the buffer may be a null pointer.
wb.buff[wb.buff_cur] = '\0';
return (ret < 0 || static_cast<size_t>(ret) > buffsz) ? 0 : ret;
}

} // namespace LIBC_NAMESPACE_DECL
23 changes: 23 additions & 0 deletions libc/src/time/strftime.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//===-- Implementation header of strftime -----------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_TIME_STRFTIME_H
#define LLVM_LIBC_SRC_TIME_STRFTIME_H

#include "hdr/types/size_t.h"
#include "hdr/types/struct_tm.h"
#include "src/__support/macros/config.h"

namespace LIBC_NAMESPACE_DECL {

size_t strftime(char *__restrict, size_t max, const char *__restrict format,
const struct tm *timeptr);

} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_TIME_STRFTIME_H
51 changes: 51 additions & 0 deletions libc/src/time/strftime_core/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
add_header_library(
core_structs
HDRS
core_structs.h
DEPENDS
libc.src.__support.CPP.string_view
libc.hdr.types.struct_tm
)

add_header_library(
parser
HDRS
parser.h
DEPENDS
.core_structs
libc.src.__support.CPP.string_view
libc.src.__support.ctype_utils
libc.src.__support.str_to_integer
)

add_object_library(
converter
SRCS
converter.cpp
HDRS
converter.h
num_converter.h
# str_converter.h
# composite_converter.h
DEPENDS
.core_structs
libc.src.time.time_utils
libc.src.time.time_constants
libc.src.stdio.printf_core.writer
libc.src.__support.CPP.string_view
libc.src.__support.integer_to_string
)

add_object_library(
strftime_main
SRCS
strftime_main.cpp
HDRS
strftime_main.h
DEPENDS
.core_structs
.parser
.converter
libc.src.stdio.printf_core.writer
libc.hdr.types.struct_tm
)
91 changes: 91 additions & 0 deletions libc/src/time/strftime_core/converter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//===-- Format specifier converter implmentation for strftime -------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See htto_conv.times://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/__support/macros/config.h"
#include "src/stdio/printf_core/writer.h"
#include "src/time/strftime_core/core_structs.h"

// #include "composite_converter.h"
#include "num_converter.h"
// #include "str_converter.h"

namespace LIBC_NAMESPACE_DECL {
namespace strftime_core {

int convert(printf_core::Writer *writer, const FormatSection &to_conv,
const tm *timeptr) {
// TODO: Implement the locale support.
if (to_conv.modifier != ConvModifier::none) {
return writer->write(to_conv.raw_string);
}

if (!to_conv.has_conv)
return writer->write(to_conv.raw_string);
switch (to_conv.conv_name) {
// The cases are grouped by type, then alphabetized with lowercase before
// uppercase.

// raw conversions
case '%':
return writer->write("%");
case 'n':
return writer->write("\n");
case 't':
return writer->write("\t");

// numeric conversions
case 'C': // Century
case 'd': // Day of the month [01-31]
case 'e': // Day of the month [1-31]
case 'g': // last 2 digits of ISO year
case 'G': // ISO year
case 'H': // 24-hour format
case 'I': // 12-hour format
case 'j': // Day of the year
case 'm': // Month of the year
case 'M': // Minute of the hour
case 's': // Seconds since the epoch
case 'S': // Second of the minute
case 'u': // ISO day of the week ([1-7] starting Monday)
case 'U': // Week of the year ([00-53] week 1 starts on first *Sunday*)
case 'V': // ISO week number ([01-53], 01 is first week majority in this year)
case 'w': // Day of week ([0-6] starting Sunday)
case 'W': // Week of the year ([00-53] week 1 starts on first *Monday*)
case 'y': // Year of the Century
case 'Y': // Full year
return convert_int(writer, to_conv, timeptr);

// string conversions
case 'a': // Abbreviated weekday name
case 'A': // Full weekday name
case 'b': // Abbreviated month name
case 'B': // Full month name
case 'h': // same as %b
case 'p': // AM/PM designation
case 'z': // Timezone offset (+/-hhmm)
case 'Z': // Timezone name
// return write_str(writer, to_conv, timeptr);

// composite conversions
case 'c': // locale specified date and time
case 'D': // %m/%d/%y (month/day/year)
case 'F': // %Y-%m-%d (year-month-day)
case 'r': // %I:%M:%S %p (hour:minute:second AM/PM)
case 'R': // %H:%M (hour:minute)
case 'T': // %H:%M:%S (hour:minute:second)
case 'x': // locale specified date
case 'X': // locale specified time
// return write_composite(writer, to_conv, timeptr);
default:
return writer->write(to_conv.raw_string);
}
return 0;
}

} // namespace strftime_core
} // namespace LIBC_NAMESPACE_DECL
29 changes: 29 additions & 0 deletions libc/src/time/strftime_core/converter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//===-- Format specifier converter for strftime -----------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CONVERTER_H
#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CONVERTER_H

#include "src/stdio/printf_core/writer.h"
#include "src/time/strftime_core/core_structs.h"

#include <stddef.h>

namespace LIBC_NAMESPACE_DECL {
namespace strftime_core {

// convert will call a conversion function to convert the FormatSection into
// its string representation, and then that will write the result to the
// writer.
int convert(printf_core::Writer *writer, const FormatSection &to_conv,
const tm *timeptr);

} // namespace strftime_core
} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CONVERTER_H
Loading
Loading