147 changes: 147 additions & 0 deletions libc/src/time/time_utils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
//===-- Implementation of mktime function ---------------------------------===//
//
// 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/time_utils.h"
#include "src/__support/common.h"

#include <limits.h>

namespace __llvm_libc {
namespace time_utils {

using __llvm_libc::time_utils::TimeConstants;

static int64_t computeRemainingYears(int64_t daysPerYears,
int64_t quotientYears,
int64_t *remainingDays) {
int64_t years = *remainingDays / daysPerYears;
if (years == quotientYears)
years--;
*remainingDays -= years * daysPerYears;
return years;
}

// First, divide "total_seconds" by the number of seconds in a day to get the
// number of days since Jan 1 1970. The remainder will be used to calculate the
// number of Hours, Minutes and Seconds.
//
// Then, adjust that number of days by a constant to be the number of days
// since Mar 1 2000. Year 2000 is a multiple of 400, the leap year cycle. This
// makes it easier to count how many leap years have passed using division.
//
// While calculating numbers of years in the days, the following algorithm
// subdivides the days into the number of 400 years, the number of 100 years and
// the number of 4 years. These numbers of cycle years are used in calculating
// leap day. This is similar to the algorithm used in getNumOfLeapYearsBefore()
// and isLeapYear(). Then compute the total number of years in days from these
// subdivided units.
//
// Compute the number of months from the remaining days. Finally, adjust years
// to be 1900 and months to be from January.
int64_t UpdateFromSeconds(int64_t total_seconds, struct tm *tm) {
// Days in month starting from March in the year 2000.
static const char daysInMonth[] = {31 /* Mar */, 30, 31, 30, 31, 31,
30, 31, 30, 31, 31, 29};

if (sizeof(time_t) == 4) {
if (total_seconds < 0x80000000)
return time_utils::OutOfRange();
if (total_seconds > 0x7FFFFFFF)
return time_utils::OutOfRange();
} else {
if (total_seconds <
INT_MIN * static_cast<int64_t>(
TimeConstants::NumberOfSecondsInLeapYear) ||
total_seconds > INT_MAX * static_cast<int64_t>(
TimeConstants::NumberOfSecondsInLeapYear))
return time_utils::OutOfRange();
}

int64_t seconds = total_seconds - TimeConstants::SecondsUntil2000MarchFirst;
int64_t days = seconds / TimeConstants::SecondsPerDay;
int64_t remainingSeconds = seconds % TimeConstants::SecondsPerDay;
if (remainingSeconds < 0) {
remainingSeconds += TimeConstants::SecondsPerDay;
days--;
}

int64_t wday = (TimeConstants::WeekDayOf2000MarchFirst + days) %
TimeConstants::DaysPerWeek;
if (wday < 0)
wday += TimeConstants::DaysPerWeek;

// Compute the number of 400 year cycles.
int64_t numOfFourHundredYearCycles = days / TimeConstants::DaysPer400Years;
int64_t remainingDays = days % TimeConstants::DaysPer400Years;
if (remainingDays < 0) {
remainingDays += TimeConstants::DaysPer400Years;
numOfFourHundredYearCycles--;
}

// The reminder number of years after computing number of
// "four hundred year cycles" will be 4 hundred year cycles or less in 400
// years.
int64_t numOfHundredYearCycles =
computeRemainingYears(TimeConstants::DaysPer100Years, 4, &remainingDays);

// The reminder number of years after computing number of
// "hundred year cycles" will be 25 four year cycles or less in 100 years.
int64_t numOfFourYearCycles =
computeRemainingYears(TimeConstants::DaysPer4Years, 25, &remainingDays);

// The reminder number of years after computing number of "four year cycles"
// will be 4 one year cycles or less in 4 years.
int64_t remainingYears = computeRemainingYears(
TimeConstants::DaysPerNonLeapYear, 4, &remainingDays);

// Calculate number of years from year 2000.
int64_t years = remainingYears + 4 * numOfFourYearCycles +
100 * numOfHundredYearCycles +
400LL * numOfFourHundredYearCycles;

int leapDay =
!remainingYears && (numOfFourYearCycles || !numOfHundredYearCycles);

int64_t yday = remainingDays + 31 + 28 + leapDay;
if (yday >= TimeConstants::DaysPerNonLeapYear + leapDay)
yday -= TimeConstants::DaysPerNonLeapYear + leapDay;

int64_t months = 0;
while (daysInMonth[months] <= remainingDays) {
remainingDays -= daysInMonth[months];
months++;
}

if (months >= TimeConstants::MonthsPerYear - 2) {
months -= TimeConstants::MonthsPerYear;
years++;
}

if (years > INT_MAX || years < INT_MIN)
return time_utils::OutOfRange();

// All the data (years, month and remaining days) was calculated from
// March, 2000. Thus adjust the data to be from January, 1900.
tm->tm_year = years + 2000 - TimeConstants::TimeYearBase;
tm->tm_mon = months + 2;
tm->tm_mday = remainingDays + 1;
tm->tm_wday = wday;
tm->tm_yday = yday;

tm->tm_hour = remainingSeconds / TimeConstants::SecondsPerHour;
tm->tm_min = remainingSeconds / TimeConstants::SecondsPerMin %
TimeConstants::SecondsPerMin;
tm->tm_sec = remainingSeconds % TimeConstants::SecondsPerMin;
// TODO(rtenneti): Need to handle timezone and update of tm_isdst.
tm->tm_isdst = 0;

return 0;
}

} // namespace time_utils
} // namespace __llvm_libc
4 changes: 4 additions & 0 deletions libc/src/time/time_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ static inline time_t OutOfRange() {
return static_cast<time_t>(-1);
}

// Update the "tm" structure's year, month, etc. members from seconds.
// "total_seconds" is the number of seconds since January 1st, 1970.
extern int64_t UpdateFromSeconds(int64_t total_seconds, struct tm *tm);

} // namespace time_utils
} // namespace __llvm_libc

Expand Down
2 changes: 2 additions & 0 deletions libc/test/src/time/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ add_libc_unittest(
SUITE
libc_time_unittests
SRCS
gmtime_test.cpp
mktime_test.cpp
HDRS
TmMatcher.h
DEPENDS
libc.src.time.gmtime
libc.src.time.mktime
)
288 changes: 288 additions & 0 deletions libc/test/src/time/gmtime_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
//===-- Unittests for gmtime ----------------------------------------------===//
//
// 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/gmtime.h"
#include "src/time/time_utils.h"
#include "test/ErrnoSetterMatcher.h"
#include "test/src/time/TmMatcher.h"
#include "utils/UnitTest/Test.h"

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

using __llvm_libc::testing::ErrnoSetterMatcher::Fails;
using __llvm_libc::testing::ErrnoSetterMatcher::Succeeds;
using __llvm_libc::time_utils::TimeConstants;

TEST(LlvmLibcGmTime, OutOfRange) {
time_t seconds = 1 + INT_MAX * static_cast<int64_t>(
TimeConstants::NumberOfSecondsInLeapYear);
struct tm *tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TRUE(tm_data == nullptr);
EXPECT_EQ(llvmlibc_errno, EOVERFLOW);

llvmlibc_errno = 0;
seconds =
INT_MIN * static_cast<int64_t>(TimeConstants::NumberOfSecondsInLeapYear) -
1;
tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TRUE(tm_data == nullptr);
EXPECT_EQ(llvmlibc_errno, EOVERFLOW);
}

TEST(LlvmLibcGmTime, InvalidSeconds) {
time_t seconds = 0;
struct tm *tm_data = nullptr;
// -1 second from 1970-01-01 00:00:00 returns 1969-12-31 23:59:59.
seconds = -1;
tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TM_EQ((tm{59, // sec
59, // min
23, // hr
31, // day
12 - 1, // tm_mon starts with 0 for Jan
1969 - TimeConstants::TimeYearBase, // year
3, // wday
364, // yday
0}),
*tm_data);
// 60 seconds from 1970-01-01 00:00:00 returns 1970-01-01 00:01:00.
seconds = 60;
tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TM_EQ((tm{0, // sec
1, // min
0, // hr
1, // day
0, // tm_mon starts with 0 for Jan
1970 - TimeConstants::TimeYearBase, // year
4, // wday
0, // yday
0}),
*tm_data);
}

TEST(LlvmLibcGmTime, InvalidMinutes) {
time_t seconds = 0;
struct tm *tm_data = nullptr;
// -1 minute from 1970-01-01 00:00:00 returns 1969-12-31 23:59:00.
seconds = -TimeConstants::SecondsPerMin;
tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TM_EQ((tm{0, // sec
59, // min
23, // hr
31, // day
11, // tm_mon starts with 0 for Jan
1969 - TimeConstants::TimeYearBase, // year
3, // wday
0, // yday
0}),
*tm_data);
// 60 minutes from 1970-01-01 00:00:00 returns 1970-01-01 01:00:00.
seconds = 60 * TimeConstants::SecondsPerMin;
tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TM_EQ((tm{0, // sec
0, // min
1, // hr
1, // day
0, // tm_mon starts with 0 for Jan
1970 - TimeConstants::TimeYearBase, // year
4, // wday
0, // yday
0}),
*tm_data);
}

TEST(LlvmLibcGmTime, InvalidHours) {
time_t seconds = 0;
struct tm *tm_data = nullptr;
// -1 hour from 1970-01-01 00:00:00 returns 1969-12-31 23:00:00.
seconds = -TimeConstants::SecondsPerHour;
tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TM_EQ((tm{0, // sec
0, // min
23, // hr
31, // day
11, // tm_mon starts with 0 for Jan
1969 - TimeConstants::TimeYearBase, // year
3, // wday
0, // yday
0}),
*tm_data);
// 24 hours from 1970-01-01 00:00:00 returns 1970-01-02 00:00:00.
seconds = 24 * TimeConstants::SecondsPerHour;
tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TM_EQ((tm{0, // sec
0, // min
0, // hr
2, // day
0, // tm_mon starts with 0 for Jan
1970 - TimeConstants::TimeYearBase, // year
5, // wday
0, // yday
0}),
*tm_data);
}

TEST(LlvmLibcGmTime, InvalidYear) {
// -1 year from 1970-01-01 00:00:00 returns 1969-01-01 00:00:00.
time_t seconds =
-TimeConstants::DaysPerNonLeapYear * TimeConstants::SecondsPerDay;
struct tm *tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TM_EQ((tm{0, // sec
0, // min
0, // hr
1, // day
0, // tm_mon starts with 0 for Jan
1969 - TimeConstants::TimeYearBase, // year
3, // wday
0, // yday
0}),
*tm_data);
}

TEST(LlvmLibcGmTime, InvalidMonths) {
time_t seconds = 0;
struct tm *tm_data = nullptr;
// -1 month from 1970-01-01 00:00:00 returns 1969-12-01 00:00:00.
seconds = -31 * TimeConstants::SecondsPerDay;
tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TM_EQ((tm{0, // sec
0, // min
0, // hr
1, // day
12 - 1, // tm_mon starts with 0 for Jan
1969 - TimeConstants::TimeYearBase, // year
1, // wday
0, // yday
0}),
*tm_data);
// 1970-13-01 00:00:00 returns 1971-01-01 00:00:00.
seconds = TimeConstants::DaysPerNonLeapYear * TimeConstants::SecondsPerDay;
tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TM_EQ((tm{0, // sec
0, // min
0, // hr
1, // day
0, // tm_mon starts with 0 for Jan
1971 - TimeConstants::TimeYearBase, // year
5, // wday
0, // yday
0}),
*tm_data);
}

TEST(LlvmLibcGmTime, InvalidDays) {
time_t seconds = 0;
struct tm *tm_data = nullptr;
// -1 day from 1970-01-01 00:00:00 returns 1969-12-31 00:00:00.
seconds = -1 * TimeConstants::SecondsPerDay;
tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TM_EQ((tm{0, // sec
0, // min
0, // hr
31, // day
11, // tm_mon starts with 0 for Jan
1969 - TimeConstants::TimeYearBase, // year
3, // wday
0, // yday
0}),
*tm_data);

// 1970-01-32 00:00:00 returns 1970-02-01 00:00:00.
seconds = 31 * TimeConstants::SecondsPerDay;
tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TM_EQ((tm{0, // sec
0, // min
0, // hr
1, // day
0, // tm_mon starts with 0 for Jan
1970 - TimeConstants::TimeYearBase, // year
0, // wday
0, // yday
0}),
*tm_data);

// 1970-02-29 00:00:00 returns 1970-03-01 00:00:00.
seconds = 59 * TimeConstants::SecondsPerDay;
tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TM_EQ((tm{0, // sec
0, // min
0, // hr
1, // day
2, // tm_mon starts with 0 for Jan
1970 - TimeConstants::TimeYearBase, // year
0, // wday
0, // yday
0}),
*tm_data);

// 1972-02-30 00:00:00 returns 1972-03-01 00:00:00.
seconds = ((2 * TimeConstants::DaysPerNonLeapYear) + 60) *
TimeConstants::SecondsPerDay;
tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TM_EQ((tm{0, // sec
0, // min
0, // hr
1, // day
2, // tm_mon starts with 0 for Jan
1972 - TimeConstants::TimeYearBase, // year
3, // wday
0, // yday
0}),
*tm_data);
}

TEST(LlvmLibcGmTime, EndOf32BitEpochYear) {
// Test for maximum value of a signed 32-bit integer.
// Test implementation can encode time for Tue 19 January 2038 03:14:07 UTC.
time_t seconds = 0x7FFFFFFF;
struct tm *tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TM_EQ((tm{7, // sec
14, // min
3, // hr
19, // day
0, // tm_mon starts with 0 for Jan
2038 - TimeConstants::TimeYearBase, // year
2, // wday
7, // yday
0}),
*tm_data);
}

TEST(LlvmLibcGmTime, Max64BitYear) {
if (sizeof(time_t) == 4)
return;
// Mon Jan 1 12:50:50 2170 (200 years from 1970),
time_t seconds = 6311479850;
struct tm *tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TM_EQ((tm{50, // sec
50, // min
12, // hr
1, // day
0, // tm_mon starts with 0 for Jan
2170 - TimeConstants::TimeYearBase, // year
1, // wday
50, // yday
0}),
*tm_data);

// Test for Tue Jan 1 12:50:50 in 2,147,483,647th year.
seconds = 67767976202043050;
tm_data = __llvm_libc::gmtime(&seconds);
EXPECT_TM_EQ((tm{50, // sec
50, // min
12, // hr
1, // day
0, // tm_mon starts with 0 for Jan
2147483647 - TimeConstants::TimeYearBase, // year
2, // wday
50, // yday
0}),
*tm_data);
}
18 changes: 9 additions & 9 deletions libc/test/src/time/mktime_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ TEST(LlvmLibcMkTime, FailureSetsErrno) {
EXPECT_THAT(__llvm_libc::mktime(&tm_data), Fails(EOVERFLOW));
}

TEST(LlvmLibcMkTime, MkTimesInvalidSeconds) {
TEST(LlvmLibcMkTime, InvalidSeconds) {
struct tm tm_data;
// -1 second from 1970-01-01 00:00:00 returns 1969-12-31 23:59:59.
EXPECT_THAT(call_mktime(&tm_data,
Expand Down Expand Up @@ -96,7 +96,7 @@ TEST(LlvmLibcMkTime, MkTimesInvalidSeconds) {
tm_data);
}

TEST(LlvmLibcMkTime, MktimeTestsInvalidMinutes) {
TEST(LlvmLibcMkTime, InvalidMinutes) {
struct tm tm_data;
// -1 minute from 1970-01-01 00:00:00 returns 1969-12-31 23:59:00.
EXPECT_THAT(call_mktime(&tm_data,
Expand Down Expand Up @@ -142,7 +142,7 @@ TEST(LlvmLibcMkTime, MktimeTestsInvalidMinutes) {
tm_data);
}

TEST(LlvmLibcMkTime, MktimeTestsInvalidHours) {
TEST(LlvmLibcMkTime, InvalidHours) {
struct tm tm_data;
// -1 hour from 1970-01-01 00:00:00 returns 1969-12-31 23:00:00.
EXPECT_THAT(call_mktime(&tm_data,
Expand Down Expand Up @@ -188,7 +188,7 @@ TEST(LlvmLibcMkTime, MktimeTestsInvalidHours) {
tm_data);
}

TEST(LlvmLibcMkTime, MktimeTestsInvalidYear) {
TEST(LlvmLibcMkTime, InvalidYear) {
struct tm tm_data;
// -1 year from 1970-01-01 00:00:00 returns 1969-01-01 00:00:00.
EXPECT_THAT(call_mktime(&tm_data,
Expand All @@ -214,7 +214,7 @@ TEST(LlvmLibcMkTime, MktimeTestsInvalidYear) {
tm_data);
}

TEST(LlvmLibcMkTime, MktimeTestsInvalidEndOf32BitEpochYear) {
TEST(LlvmLibcMkTime, InvalidEndOf32BitEpochYear) {
if (sizeof(size_t) != 4)
return;
struct tm tm_data;
Expand All @@ -238,7 +238,7 @@ TEST(LlvmLibcMkTime, MktimeTestsInvalidEndOf32BitEpochYear) {
Succeeds(TimeConstants::OutOfRangeReturnValue));
}

TEST(LlvmLibcMkTime, MktimeTestsInvalidMonths) {
TEST(LlvmLibcMkTime, InvalidMonths) {
struct tm tm_data;
// -1 month from 1970-01-01 00:00:00 returns 1969-12-01 00:00:00.
EXPECT_THAT(call_mktime(&tm_data,
Expand Down Expand Up @@ -285,7 +285,7 @@ TEST(LlvmLibcMkTime, MktimeTestsInvalidMonths) {
tm_data);
}

TEST(LlvmLibcMkTime, MktimeTestsInvalidDays) {
TEST(LlvmLibcMkTime, InvalidDays) {
struct tm tm_data;
// -1 day from 1970-01-01 00:00:00 returns 1969-12-31 00:00:00.
EXPECT_THAT(call_mktime(&tm_data,
Expand Down Expand Up @@ -377,7 +377,7 @@ TEST(LlvmLibcMkTime, MktimeTestsInvalidDays) {
tm_data);
}

TEST(LlvmLibcMkTime, MktimeTestsEndOf32BitEpochYear) {
TEST(LlvmLibcMkTime, EndOf32BitEpochYear) {
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.
Expand All @@ -403,7 +403,7 @@ TEST(LlvmLibcMkTime, MktimeTestsEndOf32BitEpochYear) {
tm_data);
}

TEST(LlvmLibcMkTime, MktimeTests64BitYear) {
TEST(LlvmLibcMkTime, Max64BitYear) {
if (sizeof(time_t) == 4)
return;
// Mon Jan 1 12:50:50 2170 (200 years from 1970),
Expand Down