14

I often hear the advice:

Sticking to UTC for all backend storage, logging, and internal logic is widely considered the gold standard in software engineering. You only convert to local time zones or offsets when strictly necessary for human input or display.

Is this still true in <chrono> (C++20 and later)? If not, how does <chrono> enable manipulation of and computation within time zones, and what is a concrete example of when this is useful?

6
  • 1
    Herby requesting votes to reopen. Upon writing this Q/A I discovered that the only way to answer your own question was to not label it a request for an opinion. Furthermore, this Q/A is not just opinion. It explains a coding idiom that needs to be more well known. The existing dogma that all internal logic should stick to UTC is demonstrated false in the answer. Commented yesterday
  • 4
    neither question, nor answer has nothing to do with c++, it doesn't solve some code's problem, it is language agnostic, as you mentioned in your answer The hard part is understanding your domain well enough to know which tool is right for the job. problem here is not with chrono, but general understanding of how timezones work and how to design approach, I'd suggest to move to softwareengineering.stackexchange.com Commented yesterday
  • 2
    @IłyaBursov Challenge: Solve the example problem given in the answer in a type-safe, error-resistant, readable manner in another language. The design of C++ enabled the design of <chrono> which in turn has overturned the dogma stated in the question. The dogma is likely still good advice if you're not programming in C++. Try this in C and the compiler will not help you correct minor type-o's and think-o's. And if you happen to get it right, the code will be far less maintainable. It will be hard to read, and the type system won't help you detect minor mistakes in future maintenance. Commented yesterday
  • 1
    joda time (2000x) or java.time (2014) have the same concepts (local/zoned/etc) in their design, type safety, but none of them (including your example with chrono) are error resistant, they do no help correct minor typos/thinkos, like if you forget to use get_sys_time, I'd also challenge dogma statement, while I can imagine that some people could say that, it is nowhere near "dogma", utc is great for exact points in time (like past or now), but "everyday 7:00" is not point in time, so you just cannot use utc for this, you have to "generate local time", and then use it one way or another Commented yesterday
  • Yeah, Jon Skeet knows what's what in this area. Full respect. Oh, I'm thinking nodatime, not joda time. Commented yesterday

2 Answers 2

26

Not necessarily. C++20 introduces local time arithmetic which can be very useful in a surprising number of circumstances. Local time arithmetic can be contrasted with UTC arithmetic to make your code more robust in the face of daylight saving time. I'm not saying local time arithmetic is better than UTC arithmetic. It is not. I'm just saying that sometimes it is the right tool for the job.

To illustrate, I have an example below that models the nightly routine of a popular BBQ restaurant. The restaurant opens at 7 in the morning, and closes at 10pm (22:00) in the evening. At closing time, two things happen:

  1. A security guard comes to work to keep the BBQ secure while it cooks for the next day.

  2. The smoking of the next day's meat begins. The meat must cook for exactly 9 hours; not 8, not 10. It should be ready when the restaurant opens at 7am the next morning.

So on a normal evening:

  • At 10pm the security guard arrives, and the meat is put into the smoker.

  • At 7am the next morning, the security guard leaves and the meat is removed from the smoker.

The security guard must be paid for each hour worked. And the meat must be cooked for exactly 9 hours and ready at 7am sharp.

Twice a year in America/New_York the politicians mess with this carefully crafted schedule by changing the local time discontinuously at 2am. The nightly routine must accommodate these daylight saving changes so that the guard is paid for each hour worked, and the meat is not over- or under-cooked.

#include <chrono>
#include <iostream>

void
nightly_routine(std::chrono::local_days dt, std::chrono::time_zone const* tz)
{
    using namespace std;
    using namespace chrono;

    // dt is the local midnight which starts a date

    // security guard works from 10pm on the previous day to 7am local time
    zoned_time sg_end{tz, dt + 7h};  // 7am local time

    // Subtract 9 hours in local time to always get 10pm the previous evening
    zoned_time sg_beg{tz, sg_end.get_local_time() - 9h};

    // Actual number of hours worked, usually 9h, but not always
    auto sg_hours = sg_end.get_sys_time() - sg_beg.get_sys_time();


    // bbq must cook for 9h and be ready at 7am local time
    zoned_time bbq_end{tz, dt + 7h};  // 7am local time

    // Subtract 9 hours in system time to reliably get 9h of cooking time
    // Usually this is 10pm the previous evening, but not always
    zoned_time bbq_beg{tz, bbq_end.get_sys_time() - 9h};

    // Actual number of hours cooked, always 9h
    auto bbq_hours = bbq_end.get_sys_time() - bbq_beg.get_sys_time();

    cout << "nightly routine for " << dt << " in " << tz->name() << "\n\n";

    cout << "security guard begins at   " << sg_beg << '\n';
    cout << "security guard finishes at " << sg_end << '\n';
    cout << "security guard works " << sg_hours/1h << " hours\n";

    cout << '\n';

    cout << "bbq begins cooking at   " << bbq_beg << '\n';
    cout << "bbq finishes cooking at " << bbq_end << '\n';
    cout << "bbq cooks for " << bbq_hours/1h << " hours\n\n";
}

int
main()
{
    using namespace std::chrono;
    auto tz = locate_zone("America/New_York");

    nightly_routine(local_days{January/1/2026}, tz);
    nightly_routine(local_days{Sunday[2]/March/2026}, tz);
    nightly_routine(local_days{July/1/2026}, tz);
    nightly_routine(local_days{Sunday[1]/November/2026}, tz);
    nightly_routine(local_days{December/1/2026}, tz);
}

Demo

  • Local time arithmetic is used in the sg_end constructor to add 7 hours to the local midnight to get 7am local time. If UTC arithmetic were used here, it would not result in 7am local when there is a 2am daylight saving change.

  • Local time arithmetic is used in the sg_beg constructor to subtract 9 hours in local time to get the security guard's arrival time. This will always be 22:00 local the previous evening, even when there is a 2am daylight saving change. This also could have been coded as dt - 2h, but I wanted to contrast this local time computation with the UTC computation used for finding the time to put the meat in the smoker.

  • UTC arithmetic is used to get the number of hours the security guard worked. This is usually 9 hours, but in this time zone can also be 8 hours or 10 hours.

  • The ending time for the BBQ uses the exact same local time arithmetic as the ending time for the security guard to reliably get 7am local.

  • The beginning time for the BBQ subtracts 9 hours using UTC time arithmetic. Contrast this with the computation for sg_beg which used local time arithmetic. Unlike the security guard, who may work 8, 9 or 10 hours, the meat must be smoked for exactly 9 hours. And so UTC arithmetic is now the proper tool.

  • As a sanity check, the number of hours the meat is smoked is computed in UTC time. This is an unnecessary computation as it will always be 9 hours.

The output of this program is:

nightly routine for 2026-01-01 in America/New_York

security guard begins at   2025-12-31 22:00:00 EST
security guard finishes at 2026-01-01 07:00:00 EST
security guard works 9 hours

bbq begins cooking at   2025-12-31 22:00:00 EST
bbq finishes cooking at 2026-01-01 07:00:00 EST
bbq cooks for 9 hours

nightly routine for 2026-03-08 in America/New_York

security guard begins at   2026-03-07 22:00:00 EST
security guard finishes at 2026-03-08 07:00:00 EDT
security guard works 8 hours

bbq begins cooking at   2026-03-07 21:00:00 EST
bbq finishes cooking at 2026-03-08 07:00:00 EDT
bbq cooks for 9 hours

nightly routine for 2026-07-01 in America/New_York

security guard begins at   2026-06-30 22:00:00 EDT
security guard finishes at 2026-07-01 07:00:00 EDT
security guard works 9 hours

bbq begins cooking at   2026-06-30 22:00:00 EDT
bbq finishes cooking at 2026-07-01 07:00:00 EDT
bbq cooks for 9 hours

nightly routine for 2026-11-01 in America/New_York

security guard begins at   2026-10-31 22:00:00 EDT
security guard finishes at 2026-11-01 07:00:00 EST
security guard works 10 hours

bbq begins cooking at   2026-10-31 23:00:00 EDT
bbq finishes cooking at 2026-11-01 07:00:00 EST
bbq cooks for 9 hours

nightly routine for 2026-12-01 in America/New_York

security guard begins at   2026-11-30 22:00:00 EST
security guard finishes at 2026-12-01 07:00:00 EST
security guard works 9 hours

bbq begins cooking at   2026-11-30 22:00:00 EST
bbq finishes cooking at 2026-12-01 07:00:00 EST
bbq cooks for 9 hours

One can see that, although the normal start time for smoking the meat is 10pm, sometimes it needs to go into the smoker as early as 9pm, and sometimes as late as 11pm.

The take-away here is that local time arithmetic can be very useful, in addition to arithmetic in UTC. The hard part is understanding your domain well enough to know which tool is right for the job.

Sign up to request clarification or add additional context in comments.

sg_end.get_local_time() - 9h seems to be a roundabout way of expressing local 10pm, dt - 2h seems more understandable.
Image
From the answer: "This also could have been coded as dt - 2h, but I wanted to contrast this local time computation with the UTC computation used for finding the time to put the meat in the smoker." I.e. in one case this syntax uses .get_local_time(), and in the second case we have the same syntax to subtract 9h except it uses .get_sys_time(). The results are sometimes different, and both can be right, depending on your needs. These two lines of code are the main point.
I have a lot of respect for you, Mr. Hinnant, but I think this answer is accidentally misleading. You're right that sometimes local time arithmetic is the best option, but the wording of this answer accidentally makes it sound like local time arithmetic is almost as common as UTC arithmetic. In my experience, it's best to use parse inputs to a timezone-aware structure (zoned_time), then do necessary local time math (day/hour/etc), then convert everything to UTC timepoints (time_point), and everything else, including storage, processes UTC time points, as much as reasonable.
Image
On the respect part, thank you. The feeling is mutual. You are a long-time, valued contributor to SO. My post is made into an environment where it is commonly believed that local time is never appropriate for internal logic or storage. So if I've leaned a little too hard against that viewpoint, it might be because the bolder I'm pushing against is gigantic. For local storage, RFC-9557 has recently been introduced. Here is how C++ can parse and format RFC-9557 extended (local) formats: stackoverflow.com/q/79681175/576911
Right, I understand why the post was written the way it was, in the context of the question. I merely meant that this answer could be improved by clarifying that "While use of local time should indeed be quite rare, there are occasionally places where it is the best tool in non-UI code. These are usually, but not always, in logic code close to the UI layer, to minimize potential timezone errors, but they do exist."
Image
My final paragraph is all I'm willing to say about the relative frequency of the need for local vs UTC computation. I'm not saying one is needed more than the other. I'm saying that they are both valid tools in the toolbox. Some domains will no doubt use one more than the other. I'm not going to try to tell the programmer something about his domain when I have no idea what his problem space looks like.
5

TL;DR

Use UTC for all storage and calculations. Convert to/from local format only for User Experience (which <chrono> is perfectly fine for but which was quite possible before already). Be aware of the possible traps.


Your question is conflating two disjunct topics:

Sticking to UTC for all backend storage, logging, and internal logic is widely considered the gold standard in software engineering. You only convert to local time zones or offsets when strictly necessary for human input or display.

I strongly support this notion.

Date formatting differs by locale, sometimes ambigously so. 2/7/2026 could be June, or February. The year 1440 might be medieval, or current, depending on locale (and / or context). Basically the only immediately recognizable, unambiguous format here is YYYY-MM-DD. It is also naively sortable. Any other formatting would require the accompanying information of the locale active at the time of writing. That locale would then also be employed upon reading. As soon as clients might use your software under different locales, that can get ugly fast.

Locale identifiers are platform-dependent. en_US, en-US, 0x0409, or en_US.UTF-8? And what the heck is th-TH-u-ca-gregory? Is your non-UTC date handling logic up to handling each of those correctly? Are you aware that this is only the overall locale, and that the date locale of any given environment can be different from e.g. the message locale?

Time zone identifiers are ambiguous. If you think ahead and tag your timestamps with time zone identifiers, you need to be aware that for example 'CST' refers to four distinct time zones: China Standard Time, Cuba Standard Time, and two different versions of Central Standard Time (one in America, one in Australia). So your timestamps still aren't unambiguous.

That's just the usual highlight list. I could go into the proper handling of jump seconds next, i.e. when a minute has more or less than 60 seconds (which happens more often than you probably think).

The last point gets all this into sharp perspective though:

The next guy working with your timestamps might be unfamiliar with your locale. Think having some offshore freelancer looking for some specific bug, comparing database entries with logged error messages. He knows his UTC offset (hopefully). He can get your UTC offset easily enough. Knowing when exactly your timezone switches to DST and back, and whether your timestamps are properly tagged DST or not*, can easily throw off things.


UTC timestamps are universal, unambiguous, and precise. Use them for everything that is not customer-facing.

Compare:

7.6.26, 09:54 MET

"What is MET? Is that July 6th, or June 7th? Wait, that's in the summer, shouldn't that be MEST? Is that morning, or afternoon?"

2026-06-07 07:54 +0200

That's June 7th, shortly before 10 o'clock in the morning locally, 8 o'clock UTC.


Is this still true in (C++20 and later)? If not, how does enable manipulation of and computation within time zones, and what is a concrete example of when this is useful?

We had workable manipulation and computation of time zones all the way back in C! This is a completely different issue though. This is about how to turn those unambiguous UTC timestamps into local timestamps for client-facing code (if that is your decision -- if your application is about precision, a UTC timestamp might be well appreciated even by the customer).

Consciously phrased a bit more absolute as I might have, to provide a counterpoint to Howard's advice -- not because I am saying he's completely wrong, but because mine are valid points as well, and need to be considered when, as Howard wrote, you try to figure out "which tool is right for the job".
"CST" is not a timezone identifier, it is a timezone abbreviation.
...or a time zone ID, or an offset label, a zone alias for a canonical time zone id, or a short display name...
This seems to be assuming that dates are stored as text, rather than a strongly typed object that includes timezone information.
(A) Good catch! (B) Sidenote: Few databases actually store dates as unix time. SQLite, MongoDB, and CouchDB store as text. MySql stores TIMESTAMP and DATETIME as structs, but by default converts DATETIME to/from server local time at runtime. Oracle stores TIMESTAMPs as structs, default server local time. SQLServer stores DATETIME(2) as structs without timezone conversions, and DATETIMEOFFSET as struct with timezone. Postgres stores TIMESTAMP as an int64 with no timezone conversion, and TIMESTAMPZ as UTC int64.
Image
So how do you suggest we handle arithmetic over DST shifts using only UTC when we may want to sometimes include and sometimes exclude the extra hour as in Howard's example?
Image
Each event in the story happens at a point in time. Those points don't care about the timezone. When deciding which points in time they are, one must take the timezone into account. In general, a fixed moment in time (certainly in the past, but also in the future if it's actually fixed) is appropriate to store in UTC. A hypothetical future moment in time (or a recurring schedule), such as "the guard will go on schedule at 10PM", may be appropriate to store in local time, if what you desire is that the actual moment is not fixed, but changes with the social/legal interpretation.
Image
For something like "the meeting is every Tuesday at 11 AM", many people will claim this is obviously defined in local time, and the meeting should still be at 11 AM local time after DST changes. To those people I say: have you ever had a scheduled meeting with people from multiple timezones that had different DST? Your problem here is not in the representation, it's out in the world, and no clever representation can save you.
"2/7/2026 could be June, or February." More likely July than June.

Your Answer

Draft saved
Draft discarded

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.