I'll express a possibly controversial opinion here.
And as the coder, how much time is it reasonable to spend preening the
code?
Almost none if you ask me! I've seen companies hire whole new teams of developers to maintain legacy codebases in ways that went beyond treating the legacy codebase as more than a black box -- as something to swim through the implementation details... and after a decade the new team still wasn't anywhere near as fluent as the original authors of the code. Meanwhile as they tried to preen and poke and prod at the implementation details of the legacy code, the software become increasingly buggy in existing, old features, not in the new ones, as the developers were trying to modify old code they couldn't possibly comprehend as well as the original authors.
This is for non-trivial code, of course, and generally code which wasn't exactly engineered against any reasonable standards.
In my opinion such legacy code should be treated as an opaque library, to be called, not modified. Old code, provided it isn't working its way towards complete obsolescence, should increasingly work its way to becoming a black box -- a stable package meant to be used, not changed. Interfaces should be identified and extracted, tests for correctness should be created where possible, and implementations should not be touched let alone fully understood unless they absolutely require changes (ex: they're buggy)... at which point if they're buggy and frequently need updates to implementation details, I'd consider replacing that unreliable section of legacy code with something new (same interface but new implementation) as potentially being faster than hoping some new developer will be able to comprehend old code which the original author didn't even write correctly and test properly. Trying to comprehend code which doesn't even work properly written by someone else ages ago is often a very fruitless endeavor. It's like trying to figure out how a combustion engine works by reverse engineering a broken one whose design was always prone to cause the engine to randomly explode.
As for the ability to decipher code, I often see it as a function of the interface. A well-documented interface where each function causes no more than a single side effect will often yield an implementation that's proportionally easy to understand. Meanwhile a counter-intuitive interface which, itself, is difficult to understand and use will often have implementations that are proportionally difficult to understand. What something is supposed to do for all possible input cases is the very first thing to understand before understanding how it does it, and some interfaces are actually so complex with so many disparate side effects that it's difficult to even answer the "what" question without raising additional "what if?" questions for tricky edge cases. In that case, the implementation will typically be as hopeless to decipher as the interface.
Of course sometimes a well-documented, clear interface can still use a very complex algorithm for the implementation, but relatively speaking, that source code will still often be relatively easier to understand if the interface is easy to understand. The first thing I'd want to do before wading inside someone else's implementation details is to study the interface required to interact with their code and ideally construct a test to check my assumptions and to make sure the interface is properly fulfilling its documented requirements. If the interface is simple and I can easily comprehend it, often the code implementing the interface will be as well and, if not, because the interface is so clear and easy to understand, it'll take little time to come up with a new implementation which fulfills the identical requirements.
As for the ultimate question about time required, I'd first want to do all the things mentioned above. There's some time needed upfront which is variable but not too variable to even figure out how to make the next time estimate if the goal is to familiarize ourselves with a legacy codebase.