//===-- Unittests for mktime ----------------------------------------------===//
//
// 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 "src/time/mktime.h"
#include "test/ErrnoSetterMatcher.h"
#include "utils/UnitTest/Test.h"

#include <errno.h>
#include <string.h>

using __llvm_libc::testing::ErrnoSetterMatcher::Fails;

static constexpr time_t OutOfRangeReturnValue = -1;

// A helper function to initialize tm data structure.
static inline void initialize_tm_data(struct tm *tm_data, int year, int month,
                                      int mday, int hour, int min, int sec) {
  struct tm temp = {.tm_sec = sec,
                    .tm_min = min,
                    .tm_hour = hour,
                    .tm_mday = mday,
                    .tm_mon = month,
                    .tm_year = year - 1900};
  *tm_data = temp;
}

static inline time_t call_mktime(struct tm *tm_data, int year, int month,
                                 int mday, int hour, int min, int sec) {
  initialize_tm_data(tm_data, year, month, mday, hour, min, sec);
  return __llvm_libc::mktime(tm_data);
}

TEST(LlvmLibcMkTime, FailureSetsErrno) {
  struct tm tm_data;
  initialize_tm_data(&tm_data, 0, 0, 0, 0, 0, -1);
  EXPECT_THAT(__llvm_libc::mktime(&tm_data), Fails(EOVERFLOW));
}

TEST(LlvmLibcMkTime, MktimeTestsInvalidSeconds) {
  struct tm tm_data;
  EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, 0, 0, -1), OutOfRangeReturnValue);
  EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, 0, 0, 60), OutOfRangeReturnValue);
}

TEST(LlvmLibcMkTime, MktimeTestsInvalidMinutes) {
  struct tm tm_data;
  EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, 0, -1, 0), OutOfRangeReturnValue);
  EXPECT_EQ(call_mktime(&tm_data, 0, 0, 1, 0, 60, 0), OutOfRangeReturnValue);
}

TEST(LlvmLibcMkTime, MktimeTestsInvalidHours) {
  struct tm tm_data;
  EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, -1, 0, 0), OutOfRangeReturnValue);
  EXPECT_EQ(call_mktime(&tm_data, 0, 0, 0, 24, 0, 0), OutOfRangeReturnValue);
}

TEST(LlvmLibcMkTime, MktimeTestsInvalidYear) {
  struct tm tm_data;
  EXPECT_EQ(call_mktime(&tm_data, 1969, 0, 0, 0, 0, 0), OutOfRangeReturnValue);
}

TEST(LlvmLibcMkTime, MktimeTestsInvalidEndOf32BitEpochYear) {
  if (sizeof(time_t) != 4)
    return;
  struct tm tm_data;
  // 2038-01-19 03:14:08 tests overflow of the second in 2038.
  EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 19, 3, 14, 8),
            OutOfRangeReturnValue);
  // 2038-01-19 03:15:07 tests overflow of the minute in 2038.
  EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 19, 3, 15, 7),
            OutOfRangeReturnValue);
  // 2038-01-19 04:14:07 tests overflow of the hour in 2038.
  EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 19, 4, 14, 7),
            OutOfRangeReturnValue);
  // 2038-01-20 03:14:07 tests overflow of the day in 2038.
  EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 20, 3, 14, 7),
            OutOfRangeReturnValue);
  // 2038-02-19 03:14:07 tests overflow of the month in 2038.
  EXPECT_EQ(call_mktime(&tm_data, 2038, 1, 19, 3, 14, 7),
            OutOfRangeReturnValue);
  // 2039-01-19 03:14:07 tests overflow of the year.
  EXPECT_EQ(call_mktime(&tm_data, 2039, 0, 19, 3, 14, 7),
            OutOfRangeReturnValue);
}

TEST(LlvmLibcMkTime, MktimeTestsInvalidMonths) {
  struct tm tm_data;
  // Before Jan of 1970
  EXPECT_EQ(call_mktime(&tm_data, 1970, -1, 15, 0, 0, 0),
            OutOfRangeReturnValue);
  // After Dec of 1970
  EXPECT_EQ(call_mktime(&tm_data, 1970, 12, 15, 0, 0, 0),
            OutOfRangeReturnValue);
}

TEST(LlvmLibcMkTime, MktimeTestsInvalidDays) {
  struct tm tm_data;
  // -1 day of Jan, 1970
  EXPECT_EQ(call_mktime(&tm_data, 1970, 0, -1, 0, 0, 0), OutOfRangeReturnValue);
  // 32 day of Jan, 1970
  EXPECT_EQ(call_mktime(&tm_data, 1970, 0, 32, 0, 0, 0), OutOfRangeReturnValue);
  // 29 day of Feb, 1970
  EXPECT_EQ(call_mktime(&tm_data, 1970, 1, 29, 0, 0, 0), OutOfRangeReturnValue);
  // 30 day of Feb, 1972
  EXPECT_EQ(call_mktime(&tm_data, 1972, 1, 30, 0, 0, 0), OutOfRangeReturnValue);
  // 31 day of Apr, 1970
  EXPECT_EQ(call_mktime(&tm_data, 1970, 3, 31, 0, 0, 0), OutOfRangeReturnValue);
}

TEST(LlvmLibcMkTime, MktimeTestsStartEpochYear) {
  // Thu Jan 1 00:00:00 1970
  struct tm tm_data;
  EXPECT_EQ(call_mktime(&tm_data, 1970, 0, 1, 0, 0, 0), static_cast<time_t>(0));
  EXPECT_EQ(4, tm_data.tm_wday);
  EXPECT_EQ(0, tm_data.tm_yday);
}

TEST(LlvmLibcMkTime, MktimeTestsEpochYearRandomTime) {
  // Thu Jan 1 12:50:50 1970
  struct tm tm_data;
  EXPECT_EQ(call_mktime(&tm_data, 1970, 0, 1, 12, 50, 50),
            static_cast<time_t>(46250));
  EXPECT_EQ(4, tm_data.tm_wday);
  EXPECT_EQ(0, tm_data.tm_yday);
}

TEST(LlvmLibcMkTime, MktimeTestsEndOf32BitEpochYear) {
  struct tm tm_data;
  // Test for maximum value of a signed 32-bit integer.
  // Test implementation can encode time for Tue 19 January 2038 03:14:07 UTC.
  EXPECT_EQ(call_mktime(&tm_data, 2038, 0, 19, 3, 14, 7),
            static_cast<time_t>(0x7FFFFFFF));
  EXPECT_EQ(2, tm_data.tm_wday);
  EXPECT_EQ(18, tm_data.tm_yday);
}

TEST(LlvmLibcMkTime, MktimeTests64BitYear) {
  if (sizeof(time_t) == 4)
    return;
  // Mon Jan 1 12:50:50 2170
  struct tm tm_data;
  EXPECT_EQ(call_mktime(&tm_data, 2170, 0, 1, 12, 50, 50),
            static_cast<time_t>(6311479850));
  EXPECT_EQ(1, tm_data.tm_wday);
  EXPECT_EQ(0, tm_data.tm_yday);

  // Test for Tue Jan 1 12:50:50 in 2,147,483,647th year.
  EXPECT_EQ(call_mktime(&tm_data, 2147483647, 0, 1, 12, 50, 50),
            static_cast<time_t>(67767976202043050));
  EXPECT_EQ(2, tm_data.tm_wday);
  EXPECT_EQ(0, tm_data.tm_yday);
}
