diff --git a/source/parser/parser_expressions.cpp b/source/parser/parser_expressions.cpp index c421cb02c..0d85119cd 100644 --- a/source/parser/parser_expressions.cpp +++ b/source/parser/parser_expressions.cpp @@ -1115,7 +1115,41 @@ void Parser::Parse_Num_Factor (EXPRESS& Express,int *Terms) { static boost::posix_time::ptime y2k(boost::gregorian::date(2000,1,1)); boost::posix_time::ptime now(boost::posix_time::microsec_clock::universal_time()); - Val = (now-y2k).total_microseconds() * (1.0e-6) / (24*60*60); + + // Due to a bug in `boost::posix_time::microsec_clock::universal_time()`, + // the value returned may actually be local time rather than UTC. We try to fix this + // by comparing with `boost::posix_time::second_clock::universal_time()`, which is + // less precise but more reliable in terms of time zone behavior. + // (NB: While we could theoretically compute the offset once, and then re-use it every time + // `new` is invoked, this would cause issues when rendering a scene during transition to or + // from daylight savings time, or other events affecting the time zone. The current approach + // is immune to such issues.) + boost::posix_time::ptime lowPrecisionNow(boost::posix_time::second_clock::universal_time()); + // The difference between the two timers, rounded to quarters of an hour, + // should correspond to the time zone offset (in seconds in this case). + const auto tzPrecisionInSeconds = 15 * 60; + int_least32_t tzOffset = std::lround(float((now - lowPrecisionNow).total_seconds()) / tzPrecisionInSeconds) * tzPrecisionInSeconds; + // Unless someone paused the code in between the above statements, the resulting difference + // should be our time zone offset in seconds (unless we're running on a system that's not + // subject to the bug, in which case the resulting difference should be zero). + if (tzOffset != 0) + { + if (sceneData->EffectiveLanguageVersion() < 380) + { + WarningOnce(kWarningGeneral, HERE, + "In POV-Ray v3.7, on some platforms 'now' erroneously evaluated to days since " + "2000-01-01 00:00 local time instead of UTC. For backward compatibility, this " + "bug is reproduced in legacy scenes, but the scene may produce different " + "results on other platforms."); + } + else + { + now -= boost::posix_time::time_duration(0, 0, tzOffset); + } + } + + const auto daysPerMicrosecond = 1.0e-6 / (24 * 60 * 60); + Val = (now - y2k).total_microseconds() * daysPerMicrosecond; } break; }