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
144 lines
4.8 KiB
C++
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);
|
|
}
|
|
}
|