Skip to content

<chrono>: Incorrect output formatting negative years #3166

Description

@blippyblips

Describe the bug
Incorrect values when formatting negative std::chrono::years with %y (last 2 only).

Command-line test case

C:\Temp2>type repro.cpp
#include <chrono>
#include <format>
#include <iostream>

int main() {
 std::chrono::year y1{-3053};
 std::cout << std::format("{:%y}", y1);
 return 0;
}
C:\Temp2>cl /EHsc /W4 /WX /std:c++20 .\repro.cpp & repro.exe
Microsoft (R) C/C++ Optimizing Compiler Version 19.33.31630 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

repro.cpp
Microsoft (R) Incremental Linker Version 14.33.31630.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:repro.exe
repro.obj
47

Expected behavior
Would expect it to print 53

STL version
Microsoft Visual Studio Community 2022 Version 17.3.6 https://github.com/microsoft/STL/tree/main

Additional context
Current code in master:
https://github.com/microsoft/STL/blob/2f8342a3a57fb157d881c6a2d42a917d20e413f8/stl/inc/chrono#L3168

Not sure about the reason for the +100 currently there but would expect something along these lines

_NODISCARD static pair<int, int> _Decompose_year(const int _Year)
{
    int _Two_d_year = _Year % 100;
    return { _Year - _Two_d_year, abs(_Two_d_year) };
}

Activity

  1. MattStephanson commented on Oct 21, 2022

    @MattStephanson
    Contributor

    I think this is the intended behavior. The %C century flag is specified in [tab:time.format.spec] as "[t]he year divided by 100 using floored division." So for -3053, that would give -31, and our convention is that the year is always century_part*100+two_digit_year, with an addition regardless of the sign of the century or year. I think this makes the most sense, given the floored division specification.

  2. blippyblips commented on Oct 21, 2022

    @blippyblips
    Author

    I agree for %C this is doing the correct thing but this same function is also being used as part of the implementation for %y.
    The last two decimal digits of the year (https://eel.is/c++draft/tab:time.format.spec#row-36-column-2-sentence-1).
    In that context the pair.second from _Decompose_year is being used and it's only correct for positive inputs.

  3. MattStephanson commented on Oct 22, 2022

    @MattStephanson
    Contributor

    You're right that the standard says "[t]he last two decimal digits of the year." But in practice this is often interpreted to mean the number of years since the beginning of the century. Obviously these meanings are the same when the year is positive and different when it's negative, so I suspect people sometimes say one thing while meaning another.

    The original proposal claims "strftime-like formatting", so we can look to that as a guide for existing practice. You can see at https://godbolt.org/z/Gbz7harWd that the format string "%C %y gives -31 47, which agrees with MSVC STL. On the other hand, {fmtlib}, which is also influential as to existing practice, gives -30 53. This disagrees with the standard as to %C and with strftime as to %y. It also formats the first centuries BCE and CE as -0 and 00, which seems less than ideal for an integer value.

    On the whole, there doesn't seems to be a clear consensus for what the result should be, so maybe an LWG issue is needed to clarify.

  4. blippyblips commented on Oct 22, 2022

    @blippyblips
    Author

    It does sound like there should be separate "years since start of century" "nearest century" "truncated century" and "last two decimals of the year" identifiers, even if they are the same in many cases it's really useful for expressing intent.

    C Standard
    C++ Time Format for reference

    The C17 draft has slightly different phrasing then the eel.is c++ draft but it matches closer with fmts implementation of the formatting identifiers, specifically it calls for the truncated integer after division by 100 instead of floored division for %C and for %y it mentions that it's 'replaced' by the last 2 digits of the year, that seems much more strictly defined.

    About the 00 case in FMT, that seems to come from 2 things,

    1. When writing negative numbers they seem to have interpreted the standard saying 4 digits as 4 characters and they remove 1 when adding the - sign, otherwise it would also have written -00.
    2. %Y for positive (0 included) is written in terms of %C%y so you're getting one zero from the century and another from the "decade/unit years"

    Year formatting in fmt for reference

    It makes more sense when you read the 2 different versions of the time specs(c vs c++) and try to combine them.

  5. StephanTLavavej commented on Oct 26, 2022

    @StephanTLavavej
    Member

    We talked about this the weekly maintainer meeting and agree that an LWG issue seems necessary here. Matt Stephanson (@MattStephanson) would you like to file an issue, or would you like someone else to look into drafting one?

  6. MattStephanson commented on Oct 26, 2022

    @MattStephanson
    Contributor

    Stephan T. Lavavej (@StephanTLavavej) --- I'll write it up and see if Victor or Howard want to add anything before it's submitted.

  7. MattStephanson commented on Nov 9, 2022

    @MattStephanson
    Contributor

    Update for anyone who's watching this issue: I sent out the draft issue yesterday. Waiting to see if one more person wants to comment, but planning to send to LWG by next Monday at the latest.

  8. MattStephanson commented on Nov 20, 2022

    @MattStephanson
    Contributor
  9. added
    bugSomething isn't working
    fixedSomething works now, yay!
    and removed
    blocked on LWGWaiting for WG21 to tell us what to do
    on Apr 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingchronoC++20 chronofixedSomething works now, yay!

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions