202 lines · c
1//===-- Numeric converter for strftime --------------------------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See htto_conv.times://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#ifndef LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_NUM_CONVERTER_H10#define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_NUM_CONVERTER_H11 12#include "hdr/types/struct_tm.h"13#include "src/__support/CPP/string_view.h"14#include "src/__support/integer_to_string.h"15#include "src/__support/macros/config.h"16#include "src/stdio/printf_core/writer.h"17#include "src/time/strftime_core/core_structs.h"18#include "src/time/time_constants.h"19#include "src/time/time_utils.h"20 21namespace LIBC_NAMESPACE_DECL {22namespace strftime_core {23 24using DecFmt = IntegerToString<uintmax_t>;25 26struct IntFormatSection {27 uintmax_t num = 0;28 char sign_char = '\0';29 size_t pad_to_len = 0;30 char padding_char = '0';31};32 33template <printf_core::WriteMode write_mode>34LIBC_INLINE int write_padded_int(printf_core::Writer<write_mode> *writer,35 const IntFormatSection &num_info) {36 37 DecFmt d(num_info.num);38 auto str = d.view();39 40 size_t digits_written = str.size();41 42 // one less digit of padding if there's a sign char43 int zeroes = static_cast<int>(num_info.pad_to_len - digits_written -44 (num_info.sign_char == 0 ? 0 : 1));45 46 // Format is (sign) (padding) digits47 if (num_info.sign_char != 0)48 RET_IF_RESULT_NEGATIVE(writer->write(num_info.sign_char));49 if (zeroes > 0)50 RET_IF_RESULT_NEGATIVE(writer->write(num_info.padding_char, zeroes))51 RET_IF_RESULT_NEGATIVE(writer->write(str));52 53 return WRITE_OK;54}55 56LIBC_INLINE IntFormatSection get_int_format(const FormatSection &to_conv,57 const tm *timeptr) {58 const time_utils::TMReader time_reader(timeptr);59 60 intmax_t raw_num;61 62 IntFormatSection result = {0, 0, 0, '0'};63 64 // gets_plus_sign is only true for year conversions where the year would be65 // positive and more than 4 digits, including leading spaces. Both the66 // FORCE_SIGN flag and gets_plus_sign must be true for a plus sign to be67 // output.68 bool gets_plus_sign = false;69 70 switch (to_conv.conv_name) {71 case 'C': // Century [00-99]72 raw_num = time_reader.get_year() / 100;73 gets_plus_sign = raw_num > 99 || to_conv.min_width > 2;74 result.pad_to_len = 2;75 break;76 case 'd': // Day of the month [01-31]77 raw_num = time_reader.get_mday(); // get_mday is 1 indexed78 result.pad_to_len = 2;79 break;80 case 'e': // Day of the month [1-31]81 raw_num = time_reader.get_mday(); // get_mday is 1 indexed82 result.pad_to_len = 2;83 result.padding_char = ' ';84 break;85 case 'g': // last 2 digits of ISO year [00-99]86 raw_num = time_reader.get_iso_year() % 100;87 result.pad_to_len = 2;88 break;89 case 'G': // ISO year90 raw_num = time_reader.get_iso_year();91 gets_plus_sign = raw_num > 9999 || to_conv.min_width > 4;92 result.pad_to_len = 4;93 break;94 case 'H': // 24-hour format [00-23]95 raw_num = time_reader.get_hour();96 result.pad_to_len = 2;97 break;98 case 'I': // 12-hour format [01-12]99 raw_num = ((time_reader.get_hour() + 11) % 12) + 1;100 result.pad_to_len = 2;101 break;102 case 'j': // Day of the year [001-366]103 raw_num = time_reader.get_yday() + 1; // get_yday is 0 indexed104 result.pad_to_len = 3;105 break;106 case 'm': // Month of the year [01-12]107 raw_num = time_reader.get_mon() + 1; // get_mon is 0 indexed108 result.pad_to_len = 2;109 break;110 case 'M': // Minute of the hour [00-59]111 raw_num = time_reader.get_min();112 result.pad_to_len = 2;113 break;114 case 's': // Seconds since the epoch115 raw_num = time_reader.get_epoch();116 result.pad_to_len = 0;117 break;118 case 'S': // Second of the minute [00-60]119 raw_num = time_reader.get_sec();120 result.pad_to_len = 2;121 break;122 case 'u': // ISO day of the week ([1-7] starting Monday)123 raw_num = time_reader.get_iso_wday() + 1;124 // need to add 1 because get_iso_wday returns the weekday [0-6].125 result.pad_to_len = 1;126 break;127 case 'U': // Week of the year ([00-53] week 1 starts on first *Sunday*)128 // This doesn't actually end up using tm_year, despite the standard saying129 // it's needed. The end of the current year doesn't really matter, so leap130 // years aren't relevant. If this is wrong, please tell me what I'm missing.131 raw_num = time_reader.get_week(time_constants::SUNDAY);132 result.pad_to_len = 2;133 break;134 case 'V': // ISO week number ([01-53], 01 is first week majority in this year)135 // This does need to know the year, since it may affect what the week of the136 // previous year it underflows to.137 raw_num = time_reader.get_iso_week();138 result.pad_to_len = 2;139 break;140 case 'w': // Day of week ([0-6] starting Sunday)141 raw_num = time_reader.get_wday();142 result.pad_to_len = 1;143 break;144 case 'W': // Week of the year ([00-53] week 1 starts on first *Monday*)145 raw_num = time_reader.get_week(time_constants::MONDAY);146 result.pad_to_len = 2;147 break;148 case 'y': // Year of the Century [00-99]149 raw_num = time_reader.get_year() % 100;150 result.pad_to_len = 2;151 break;152 case 'Y': // Full year153 raw_num = time_reader.get_year();154 gets_plus_sign = raw_num > 9999 || to_conv.min_width > 4;155 result.pad_to_len = 4;156 break;157 case 'z': // Timezone offset [+/-HHMM]158 raw_num = time_reader.get_timezone_offset();159 result.sign_char = '+'; // force the '+' sign iff raw_num is non-negative160 result.pad_to_len = 5; // 4 + 1 for the sign161 break;162 default:163 __builtin_trap(); // this should be unreachable, but trap if you hit it.164 }165 166 result.num = static_cast<uintmax_t>(raw_num < 0 ? -raw_num : raw_num);167 const bool is_negative = raw_num < 0;168 169 // TODO: Handle locale modifiers170 171 if ((to_conv.flags & FormatFlags::LEADING_ZEROES) ==172 FormatFlags::LEADING_ZEROES)173 result.padding_char = '0';174 175 if (is_negative)176 result.sign_char = '-';177 else if ((to_conv.flags & FormatFlags::FORCE_SIGN) ==178 FormatFlags::FORCE_SIGN &&179 gets_plus_sign)180 result.sign_char = '+';181 182 // sign isn't a problem because we're taking the max. The result is always183 // non-negative. Also min_width can only be 0 if it's defaulted, since 0 is a184 // flag.185 if (to_conv.min_width > 0)186 result.pad_to_len = to_conv.min_width;187 188 return result;189}190 191template <printf_core::WriteMode write_mode>192LIBC_INLINE int convert_int(printf_core::Writer<write_mode> *writer,193 const FormatSection &to_conv, const tm *timeptr) {194 195 return write_padded_int(writer, get_int_format(to_conv, timeptr));196}197 198} // namespace strftime_core199} // namespace LIBC_NAMESPACE_DECL200 201#endif202