Jean Perier b3e392c081 [flang] Implement Posix version of DATE_AND_TIME runtime
Use gettimeofday and localtime_r to implement DATE_AND_TIME intrinsic.
The Windows version fallbacks to the "no date and time information
available" defined by the standard (strings set to blanks and values to
-HUGE).

The implementation uses an ifdef between windows and the rest because
from my tests, the SFINAE approach leads to undeclared name bogus errors
with clang 8 that seems to ignore failure to instantiate is not an error
for the function names (i.e., it understands it should not instantiate
the version using gettimeofday if it is not there, but still yields an
error that it is not declared on the spot where it is called in the
uninstantiated version).

Differential Revision: https://reviews.llvm.org/D108622
2021-08-25 11:16:52 +02:00

144 lines
4.8 KiB
C++

//===-- flang/unittests/RuntimeGTest/Time.cpp -----------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "gtest/gtest.h"
#include "../../runtime/time-intrinsic.h"
#include <algorithm>
#include <cctype>
#include <charconv>
#include <string>
using namespace Fortran::runtime;
TEST(TimeIntrinsics, CpuTime) {
// We can't really test that we get the "right" result for CPU_TIME, but we
// can have a smoke test to see that we get something reasonable on the
// platforms where we expect to support it.
double start{RTNAME(CpuTime)()};
ASSERT_GE(start, 0.0);
// Loop until we get a different value from CpuTime. If we don't get one
// before we time out, then we should probably look into an implementation
// for CpuTime with a better timer resolution.
for (double end = start; end == start; end = RTNAME(CpuTime)()) {
ASSERT_GE(end, 0.0);
ASSERT_GE(end, start);
}
}
using count_t = CppTypeFor<TypeCategory::Integer, 8>;
TEST(TimeIntrinsics, SystemClock) {
// We can't really test that we get the "right" result for SYSTEM_CLOCK, but
// we can have a smoke test to see that we get something reasonable on the
// platforms where we expect to support it.
// The value of the count rate and max will vary by platform, but they should
// always be strictly positive if we have a working implementation of
// SYSTEM_CLOCK.
EXPECT_GT(RTNAME(SystemClockCountRate)(), 0);
count_t max{RTNAME(SystemClockCountMax)()};
EXPECT_GT(max, 0);
count_t start{RTNAME(SystemClockCount)()};
EXPECT_GE(start, 0);
EXPECT_LE(start, max);
// Loop until we get a different value from SystemClockCount. If we don't get
// one before we time out, then we should probably look into an implementation
// for SystemClokcCount with a better timer resolution on this platform.
for (count_t end = start; end == start; end = RTNAME(SystemClockCount)()) {
EXPECT_GE(end, 0);
EXPECT_LE(end, max);
EXPECT_GE(end, start);
}
}
TEST(TimeIntrinsics, DateAndTime) {
constexpr std::size_t bufferSize{16};
std::string date(bufferSize, 'Z'), time(bufferSize, 'Z'),
zone(bufferSize, 'Z');
RTNAME(DateAndTime)
(date.data(), date.size(), time.data(), time.size(), zone.data(), zone.size(),
/*source=*/nullptr, /*line=*/0, /*values=*/nullptr);
auto isBlank = [](const std::string &s) -> bool {
return std::all_of(
s.begin(), s.end(), [](char c) { return std::isblank(c); });
};
// Validate date is blank or YYYYMMDD.
if (isBlank(date)) {
EXPECT_TRUE(true);
} else {
count_t number{-1};
auto [_, ec]{
std::from_chars(date.data(), date.data() + date.size(), number)};
ASSERT_TRUE(ec != std::errc::invalid_argument &&
ec != std::errc::result_out_of_range);
EXPECT_GE(number, 0);
auto year = number / 10000;
auto month = (number - year * 10000) / 100;
auto day = number % 100;
// Do not assume anything about the year, the test could be
// run on system with fake/outdated dates.
EXPECT_LE(month, 12);
EXPECT_GT(month, 0);
EXPECT_LE(day, 31);
EXPECT_GT(day, 0);
}
// Validate time is hhmmss.sss or blank.
if (isBlank(time)) {
EXPECT_TRUE(true);
} else {
count_t number{-1};
auto [next, ec]{
std::from_chars(time.data(), time.data() + date.size(), number)};
ASSERT_TRUE(ec != std::errc::invalid_argument &&
ec != std::errc::result_out_of_range);
ASSERT_GE(number, 0);
auto hours = number / 10000;
auto minutes = (number - hours * 10000) / 100;
auto seconds = number % 100;
EXPECT_LE(hours, 23);
EXPECT_LE(minutes, 59);
// Accept 60 for leap seconds.
EXPECT_LE(seconds, 60);
ASSERT_TRUE(next != time.data() + time.size());
EXPECT_EQ(*next, '.');
count_t milliseconds{-1};
ASSERT_TRUE(next + 1 != time.data() + time.size());
auto [_, ec2]{
std::from_chars(next + 1, time.data() + date.size(), milliseconds)};
ASSERT_TRUE(ec2 != std::errc::invalid_argument &&
ec2 != std::errc::result_out_of_range);
EXPECT_GE(milliseconds, 0);
EXPECT_LE(milliseconds, 999);
}
// Validate zone is +hhmm or -hhmm or blank.
if (isBlank(zone)) {
EXPECT_TRUE(true);
} else {
ASSERT_TRUE(zone.size() > 1);
EXPECT_TRUE(zone[0] == '+' || zone[0] == '-');
count_t number{-1};
auto [next, ec]{
std::from_chars(zone.data() + 1, zone.data() + zone.size(), number)};
ASSERT_TRUE(ec != std::errc::invalid_argument &&
ec != std::errc::result_out_of_range);
ASSERT_GE(number, 0);
auto hours = number / 100;
auto minutes = number % 100;
EXPECT_LE(hours, 23);
EXPECT_LE(minutes, 59);
}
}