progress: narrower time display, multiple fixes

- Each time field is now 7 characters wide, so that the total width
  never exceeds 79 columns so that it works correctly also in Windows
  terminals. The title lines are adjusted accordingly.

  This is accomplished by using h:mm:ss style up to 10 hours, and for
  longer periods switch to "nnX nnY" style output. For hours, days,
  months and years.

  For less than one hour, the hour field is now dropped.

  When no time info is provided, the field is now space-only. No more
  `-:--:--`.

  Also fixed the output for really long times which previously was
  completely broken. The largest time now shows as ">99999y". (Becase
  I can't figure out a better way).

- For sizes, the widths are now properly fixed to 6 characters. When
  displaying a unit with less than 3 digits, it shows two decimal
  precision like "16777215 => 15.99M" and one decmal otherwise: "262143
  => 255.9k"

  Also fixes the decimal math. 131071 is 127.9k, which it previously did
  not show.

- The time and size field outputs are now properly verified in test
  1636.

Fixes #20122
Closes #20173

fixup use only space when no time exists

Drop the hour from the display when zero
This commit is contained in:
Daniel Stenberg 2026-01-03 12:16:06 +01:00
parent 3c8f9c9247
commit 193397bf4e
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
5 changed files with 296 additions and 28 deletions

View File

@ -34,38 +34,62 @@
#define MIN_RATE_LIMIT_PERIOD 3000
#ifndef CURL_DISABLE_PROGRESS_METER
/* Provide a string that is 2 + 1 + 2 + 1 + 2 = 8 letters long (plus the zero
byte) */
static void time2str(char *r, size_t rsize, curl_off_t seconds)
/* Provide a string that is 7 letters long (plus the zero byte).
Unit test 1636.
*/
UNITTEST void time2str(char *r, size_t rsize, curl_off_t seconds);
UNITTEST void time2str(char *r, size_t rsize, curl_off_t seconds)
{
curl_off_t h;
if(seconds <= 0) {
curlx_strcopy(r, rsize, "--:--:--", 8);
curlx_strcopy(r, rsize, " ", 7);
return;
}
h = seconds / 3600;
if(h <= 99) {
curl_off_t m = (seconds - (h * 3600)) / 60;
curl_off_t s = (seconds - (h * 3600)) - (m * 60);
curl_msnprintf(r, rsize, "%2" FMT_OFF_T ":%02" FMT_OFF_T ":%02" FMT_OFF_T,
h, m, s);
if(h <= 9) {
curl_off_t s = (seconds - (h * 3600)) - (m * 60);
if(h)
curl_msnprintf(r, rsize, "%" FMT_OFF_T ":%02" FMT_OFF_T ":"
"%02" FMT_OFF_T, h, m, s);
else
curl_msnprintf(r, rsize, " %02" FMT_OFF_T ":%02" FMT_OFF_T, m, s);
}
else
curl_msnprintf(r, rsize, "%" FMT_OFF_T "h %02" FMT_OFF_T "m", h, m);
}
else {
/* this equals to more than 99 hours, switch to a more suitable output
format to fit within the limits. */
curl_off_t d = seconds / 86400;
h = (seconds - (d * 86400)) / 3600;
if(d <= 999)
curl_msnprintf(r, rsize, "%3" FMT_OFF_T "d %02" FMT_OFF_T "h", d, h);
else
curl_msnprintf(r, rsize, "%7" FMT_OFF_T "d", d);
if(d <= 99)
curl_msnprintf(r, rsize, "%2" FMT_OFF_T "d %02" FMT_OFF_T "h", d, h);
else if(d <= 999)
curl_msnprintf(r, rsize, "%6" FMT_OFF_T "d", d);
else { /* more than 999 days */
curl_off_t m = d / 30;
if(m <= 999)
curl_msnprintf(r, rsize, "%6" FMT_OFF_T "m", m);
else { /* more than 999 months */
curl_off_t y = d / 365;
if(y <= 99999)
curl_msnprintf(r, rsize, "%6" FMT_OFF_T "y", y);
else
curlx_strcopy(r, rsize, ">99999y", 7);
}
}
}
}
/* The point of this function would be to return a string of the input data,
but never longer than 6 columns (+ one zero byte).
Add suffix k, M, G when suitable... */
static char *max6out(curl_off_t bytes, char *max6, size_t mlen)
Add suffix k, M, G when suitable...
Unit test 1636
*/
UNITTEST char *max6out(curl_off_t bytes, char *max6, size_t mlen);
UNITTEST char *max6out(curl_off_t bytes, char *max6, size_t mlen)
{
/* a signed 64-bit value is 8192 petabytes maximum, shown as
8.0E (exabytes)*/
@ -75,6 +99,7 @@ static char *max6out(curl_off_t bytes, char *max6, size_t mlen)
const char unit[] = { 'k', 'M', 'G', 'T', 'P', 'E', 0 };
int k = 0;
curl_off_t nbytes;
curl_off_t rest;
do {
nbytes = bytes / 1024;
if(nbytes < 1000)
@ -83,10 +108,17 @@ static char *max6out(curl_off_t bytes, char *max6, size_t mlen)
k++;
DEBUGASSERT(unit[k]);
} while(unit[k]);
/* xxx.yU */
curl_msnprintf(max6, mlen, "%3" CURL_FORMAT_CURL_OFF_T
".%" CURL_FORMAT_CURL_OFF_T "%c", nbytes,
(bytes % 1024) / (1024 / 10), unit[k]);
rest = bytes % 1024;
if(nbytes <= 99)
/* xx.yyU */
curl_msnprintf(max6, mlen, "%2" CURL_FORMAT_CURL_OFF_T
".%02" CURL_FORMAT_CURL_OFF_T "%c", nbytes,
rest * 100 / 1024, unit[k]);
else
/* xxx.yU */
curl_msnprintf(max6, mlen, "%3" CURL_FORMAT_CURL_OFF_T
".%" CURL_FORMAT_CURL_OFF_T "%c", nbytes,
rest * 10 / 1024, unit[k]);
}
return max6;
}
@ -501,9 +533,9 @@ static void progress_meter(struct Curl_easy *data)
curl_off_t total_cur_size;
curl_off_t total_expected_size;
curl_off_t dl_size;
char time_left[10];
char time_total[10];
char time_spent[10];
char time_left[8];
char time_total[8];
char time_spent[8];
curl_off_t cur_secs = (curl_off_t)p->timespent / 1000000; /* seconds */
if(!p->headers_out) {
@ -514,9 +546,9 @@ static void progress_meter(struct Curl_easy *data)
}
curl_mfprintf(data->set.err,
" %% Total %% Received %% Xferd Average Speed "
"Time Time Time Current\n"
"Time Time Time Current\n"
" Dload Upload "
"Total Spent Left Speed\n");
"Total Spent Left Speed\n");
p->headers_out = TRUE; /* headers are shown */
}
@ -567,9 +599,9 @@ static void progress_meter(struct Curl_easy *data)
sizeof(max6[3])), /* avrg dl speed */
max6out(p->ul.speed, max6[4],
sizeof(max6[4])), /* avrg ul speed */
time_total, /* 8 letters */ /* total time */
time_spent, /* 8 letters */ /* time spent */
time_left, /* 8 letters */ /* time left */
time_total, /* 7 letters */ /* total time */
time_spent, /* 7 letters */ /* time spent */
time_left, /* 7 letters */ /* time left */
max6out(p->current_speed, max6[5],
sizeof(max6[5])) /* current speed */
);

View File

@ -216,7 +216,7 @@ test1606 test1607 test1608 test1609 test1610 test1611 test1612 test1613 \
test1614 test1615 test1616 test1617 \
test1620 test1621 test1622 \
\
test1630 test1631 test1632 test1633 test1634 test1635 \
test1630 test1631 test1632 test1633 test1634 test1635 test1636 \
\
test1650 test1651 test1652 test1653 test1654 test1655 test1656 test1657 \
test1658 \

161
tests/data/test1636 Normal file
View File

@ -0,0 +1,161 @@
<?xml version="1.0" encoding="US-ASCII"?>
<testcase>
<info>
<keywords>
unittest
time2str
max6out
</keywords>
</info>
<client>
<features>
unittest
</features>
<name>
time2str and max6out
</name>
<command>
%LOGDIR/%TESTNUMBER
</command>
</client>
<verify>
<stdout mode="text">
time2str
0 - %SP%SP%SP%SP%SP%SP%SP
1 - 00:01
3 - 00:03
7 - 00:07
15 - 00:15
31 - 00:31
63 - 01:03
127 - 02:07
255 - 04:15
511 - 08:31
1023 - 17:03
2047 - 34:07
4095 - 1:08:15
8191 - 2:16:31
16383 - 4:33:03
32767 - 9:06:07
65535 - 18h 12m
131071 - 36h 24m
262143 - 72h 49m
524287 - 6d 01h
1048575 - 12d 03h
2097151 - 24d 06h
4194303 - 48d 13h
8388607 - 97d 02h
16777215 - 194d
33554431 - 388d
67108863 - 776d
134217727 - 51m
268435455 - 103m
536870911 - 207m
1073741823 - 414m
2147483647 - 828m
4294967295 - 136y
8589934591 - 272y
17179869183 - 544y
34359738367 - 1089y
68719476735 - 2179y
137438953471 - 4358y
274877906943 - 8716y
549755813887 - 17432y
1099511627775 - 34865y
2199023255551 - 69730y
4398046511103 - >99999y
8796093022207 - >99999y
17592186044415 - >99999y
35184372088831 - >99999y
70368744177663 - >99999y
140737488355327 - >99999y
281474976710655 - >99999y
562949953421311 - >99999y
1125899906842623 - >99999y
2251799813685247 - >99999y
4503599627370495 - >99999y
9007199254740991 - >99999y
18014398509481983 - >99999y
36028797018963967 - >99999y
72057594037927935 - >99999y
144115188075855871 - >99999y
288230376151711743 - >99999y
576460752303423487 - >99999y
1152921504606846975 - >99999y
2305843009213693951 - >99999y
4611686018427387903 - >99999y
max6out
0 - 0
1 - 1
3 - 3
7 - 7
15 - 15
31 - 31
63 - 63
127 - 127
255 - 255
511 - 511
1023 - 1023
2047 - 2047
4095 - 4095
8191 - 8191
16383 - 16383
32767 - 32767
65535 - 65535
131071 - 127.9k
262143 - 255.9k
524287 - 511.9k
1048575 - 0.99M
2097151 - 1.99M
4194303 - 3.99M
8388607 - 7.99M
16777215 - 15.99M
33554431 - 31.99M
67108863 - 63.99M
134217727 - 127.9M
268435455 - 255.9M
536870911 - 511.9M
1073741823 - 0.99G
2147483647 - 1.99G
4294967295 - 3.99G
8589934591 - 7.99G
17179869183 - 15.99G
34359738367 - 31.99G
68719476735 - 63.99G
137438953471 - 127.9G
274877906943 - 255.9G
549755813887 - 511.9G
1099511627775 - 0.99T
2199023255551 - 1.99T
4398046511103 - 3.99T
8796093022207 - 7.99T
17592186044415 - 15.99T
35184372088831 - 31.99T
70368744177663 - 63.99T
140737488355327 - 127.9T
281474976710655 - 255.9T
562949953421311 - 511.9T
1125899906842623 - 0.99P
2251799813685247 - 1.99P
4503599627370495 - 3.99P
9007199254740991 - 7.99P
18014398509481983 - 15.99P
36028797018963967 - 31.99P
72057594037927935 - 63.99P
144115188075855871 - 127.9P
288230376151711743 - 255.9P
576460752303423487 - 511.9P
1152921504606846975 - 0.99E
2305843009213693951 - 1.99E
4611686018427387903 - 3.99E
131072 - 128.0k
12645826 - 12.05M
1073741824 - 1.00G
12938588979 - 12.04G
1099445657078333 - 999.9T
</stdout>
</verify>
</testcase>

View File

@ -37,6 +37,7 @@ TESTS_C = \
unit1600.c unit1601.c unit1602.c unit1603.c unit1605.c unit1606.c \
unit1607.c unit1608.c unit1609.c unit1610.c unit1611.c unit1612.c unit1614.c \
unit1615.c unit1616.c unit1620.c \
unit1636.c \
unit1650.c unit1651.c unit1652.c unit1653.c unit1654.c unit1655.c unit1656.c \
unit1657.c unit1658.c unit1660.c unit1661.c unit1663.c unit1664.c \
unit1979.c unit1980.c \

74
tests/unit/unit1636.c Normal file
View File

@ -0,0 +1,74 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "unitcheck.h"
#include "unitprotos.h"
static CURLcode test_unit1636(const char *arg)
{
UNITTEST_BEGIN_SIMPLE
{
char buffer[9];
curl_off_t secs;
int i;
static const curl_off_t check[] = {
/* bytes to check */
131072,
12645826,
1073741824,
12938588979,
1099445657078333,
0 /* end of list */
};
puts("time2str");
for(i = 0, secs = 0; i < 63; i++) {
time2str(buffer, sizeof(buffer), secs);
curl_mprintf("%20" FMT_OFF_T " - %s\n", secs, buffer);
if(strlen(buffer) != 7) {
curl_mprintf("^^ was too long!\n");
}
secs *= 2;
secs++;
}
puts("max6out");
for(i = 0, secs = 0; i < 63; i++) {
max6out(secs, buffer, sizeof(buffer));
curl_mprintf("%20" FMT_OFF_T " - %s\n", secs, buffer);
if(strlen(buffer) != 6) {
curl_mprintf("^^ was too long!\n");
}
secs *= 2;
secs++;
}
for(i = 0; check[i]; i++) {
secs = check[i];
max6out(secs, buffer, sizeof(buffer));
curl_mprintf("%20" FMT_OFF_T " - %s\n", secs, buffer);
if(strlen(buffer) != 6) {
curl_mprintf("^^ was too long!\n");
}
}
}
UNITTEST_END(curl_global_cleanup())
}