/* * Copyright (c) 2013, 2023 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * time/gmtime_r.c * Convert a timestamp into a date and time according to UTC. */ #include #include #include #include #define DAYS_JANUARY 31 #define DAYS_FEBRUARY 28 #define DAYS_MARCH 31 #define DAYS_APRIL 30 #define DAYS_MAY 31 #define DAYS_JUNE 30 #define DAYS_JULY 31 #define DAYS_AUGUST 31 #define DAYS_SEPTEMBER 30 #define DAYS_OCTOBER 31 #define DAYS_NOVEMBER 30 #define DAYS_DECEMBER 31 #define UNKNOWN 127 #define DECL_LEAP_SECOND(year, jun, dec) \ {0, 0, 0, 0, 0, jun, 0, 0, 0, 0, 0, dec} static const char leap_seconds[][12] = { DECL_LEAP_SECOND(1970, 0, 0), DECL_LEAP_SECOND(1971, 0, 0), DECL_LEAP_SECOND(1972, 1, 1), DECL_LEAP_SECOND(1973, 0, 1), DECL_LEAP_SECOND(1974, 0, 1), DECL_LEAP_SECOND(1975, 0, 1), DECL_LEAP_SECOND(1976, 0, 1), DECL_LEAP_SECOND(1977, 0, 1), DECL_LEAP_SECOND(1978, 0, 1), DECL_LEAP_SECOND(1979, 0, 1), DECL_LEAP_SECOND(1980, 0, 0), DECL_LEAP_SECOND(1981, 1, 0), DECL_LEAP_SECOND(1982, 1, 0), DECL_LEAP_SECOND(1983, 1, 0), DECL_LEAP_SECOND(1984, 0, 0), DECL_LEAP_SECOND(1985, 1, 0), DECL_LEAP_SECOND(1986, 0, 0), DECL_LEAP_SECOND(1987, 0, 1), DECL_LEAP_SECOND(1988, 0, 0), DECL_LEAP_SECOND(1989, 0, 1), DECL_LEAP_SECOND(1990, 0, 1), DECL_LEAP_SECOND(1991, 0, 0), DECL_LEAP_SECOND(1992, 1, 0), DECL_LEAP_SECOND(1993, 1, 0), DECL_LEAP_SECOND(1994, 1, 0), DECL_LEAP_SECOND(1995, 0, 1), DECL_LEAP_SECOND(1996, 0, 0), DECL_LEAP_SECOND(1997, 1, 0), DECL_LEAP_SECOND(1998, 0, 1), DECL_LEAP_SECOND(1999, 0, 0), DECL_LEAP_SECOND(2000, 0, 0), DECL_LEAP_SECOND(2001, 0, 0), DECL_LEAP_SECOND(2002, 0, 0), DECL_LEAP_SECOND(2003, 0, 0), DECL_LEAP_SECOND(2004, 0, 0), DECL_LEAP_SECOND(2005, 0, 1), DECL_LEAP_SECOND(2006, 0, 0), DECL_LEAP_SECOND(2007, 0, 0), DECL_LEAP_SECOND(2008, 0, 1), DECL_LEAP_SECOND(2009, 0, 0), DECL_LEAP_SECOND(2010, 0, 0), DECL_LEAP_SECOND(2011, 0, 0), DECL_LEAP_SECOND(2012, 1, 0), DECL_LEAP_SECOND(2013, 0, 0), DECL_LEAP_SECOND(2014, 0, 0), DECL_LEAP_SECOND(2015, 1, 0), DECL_LEAP_SECOND(2016, 0, 1), DECL_LEAP_SECOND(2017, 0, 0), DECL_LEAP_SECOND(2018, 0, 0), DECL_LEAP_SECOND(2019, 0, 0), DECL_LEAP_SECOND(2020, 0, 0), DECL_LEAP_SECOND(2021, 0, 0), DECL_LEAP_SECOND(2022, 0, 0), DECL_LEAP_SECOND(2023, 0, UNKNOWN), }; static const char month_days_list[12] = { DAYS_JANUARY, DAYS_FEBRUARY, DAYS_MARCH, DAYS_APRIL, DAYS_MAY, DAYS_JUNE, DAYS_JULY, DAYS_AUGUST, DAYS_SEPTEMBER, DAYS_OCTOBER, DAYS_NOVEMBER, DAYS_DECEMBER, }; static time_t get_leap_second_maybe(int year, int month) { const time_t num_years = sizeof(leap_seconds) / sizeof(leap_seconds[0]); if ( year < 1970 ) return 0; if ( num_years <= year-1970 ) return UNKNOWN; return leap_seconds[year-1970][month]; } static time_t get_leap_second(int year, int month) { time_t result = get_leap_second_maybe(year, month); return result == UNKNOWN ? 0 : result; } static time_t leap_seconds_in_year(int year) { time_t ret = 0; for ( int i = 0; i < 12; i++ ) ret += get_leap_second(year, i); return ret; } static bool is_leap_year(int year) { return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; } static time_t days_in_year(int year) { return DAYS_JANUARY + DAYS_FEBRUARY + (is_leap_year(year) ? 1 : 0) + DAYS_MARCH + DAYS_APRIL + DAYS_MAY + DAYS_JUNE + DAYS_JULY + DAYS_AUGUST + DAYS_SEPTEMBER + DAYS_OCTOBER + DAYS_NOVEMBER + DAYS_DECEMBER; } static int days_in_month(int year, int month) { return month_days_list[month] + (month == 1 && is_leap_year(year)); } struct tm* gmtime_r(const time_t* time_ptr, struct tm* ret) { time_t left = *time_ptr; ret->tm_year = 1970; ret->tm_wday = 4 /* Supposedly, the world began on a Thursday. */; // If the timestamp is after the epoch. while ( 0 < left ) { time_t year_leaps = leap_seconds_in_year(ret->tm_year); time_t year_days = days_in_year(ret->tm_year); time_t year_seconds = year_days * 24 * 60 * 60 + year_leaps; if ( year_seconds <= left ) { left -= year_seconds; ret->tm_wday = (ret->tm_wday + year_days) % 7; ret->tm_year++; continue; } break; } // If the timestamp was before the epoch. while ( left < 0 ) { ret->tm_year--; time_t year_leaps = leap_seconds_in_year(ret->tm_year); time_t year_days = days_in_year(ret->tm_year); time_t year_seconds = year_days * 24 * 60 * 60 + year_leaps; left += year_seconds; // We need to avoid taking the modulo of a negative value or the // (broken) C modulo operator gives the wrong result. ret->tm_wday = (ret->tm_wday - year_days + 7*7*7*7) % 7; } // Figure out the correct month. ret->tm_mon = 0; ret->tm_yday = 0; while ( true ) { int month_leaps = get_leap_second(ret->tm_year, ret->tm_mon); int month_days = days_in_month(ret->tm_year, ret->tm_mon); int month_seconds = month_days * 24 * 60 * 60 + month_leaps; if ( month_seconds <= left ) { left -= month_seconds; ret->tm_mon++; ret->tm_yday += month_days; ret->tm_wday = (ret->tm_wday + month_days) % 7; continue; } break; } ret->tm_mday = left / (24 * 60 * 60); left = left % (24 * 60 * 60); // If this is a regular timestamp. if ( ret->tm_mday < days_in_month(ret->tm_year, ret->tm_mon) ) { ret->tm_yday += ret->tm_mday; ret->tm_hour = left / (60 * 60); left = left % (60 * 60); ret->tm_min = left / 60; left = left % 60; ret->tm_sec = left; } // If we got the timestamp for an added leap second. else { ret->tm_mday--; // Seemingly additional day. ret->tm_yday += ret->tm_mday; ret->tm_hour = 23; ret->tm_min = 59; ret->tm_sec = 60; } ret->tm_wday = (ret->tm_wday + ret->tm_mday) % 7; // TODO: Support daylight savings and timezones. ret->tm_isdst = -1; // Fix the ranges of some of the variables. ret->tm_mday += 1; ret->tm_year -= 1900; return ret; } time_t timegm(struct tm* tm) { time_t year = tm->tm_year + 1900; time_t month = tm->tm_mon; time_t day = tm->tm_mday - 1; time_t hour = tm->tm_hour; time_t minute = tm->tm_min; time_t second = tm->tm_sec; time_t ret = 0; for ( time_t y = 1970; y < year; y++ ) { time_t year_leaps = leap_seconds_in_year(y); time_t year_days = days_in_year(y); time_t year_seconds = year_days * 24 * 60 * 60 + year_leaps; ret += year_seconds; } for ( int m = 0; m < month; m++ ) { int month_leaps = get_leap_second(year, m); int month_days = days_in_month(year, m); int month_seconds = month_days * 24 * 60 * 60 + month_leaps; ret += month_seconds; } ret += (time_t) day * 24 * 60 * 60; ret += (time_t) hour * 60 * 60; ret += (time_t) minute * 60; ret += (time_t) second * 1; gmtime_r(&ret, tm); return ret; } int sub_leap_seconds(time_t* ptr) { time_t t = *ptr; time_t next = 0; time_t offset = 0; for ( int year = 1970; true; year++ ) { for ( int month = 0; month < 12; month++ ) { time_t seconds = days_in_month(year, month) * 24 * 60 * 60; time_t leap = get_leap_second_maybe(year, month); next += seconds; if ( leap == UNKNOWN ) { *ptr = t - offset; return t < next - 1 ? 1 : -1; } next += leap; if ( t < next ) { *ptr = t - offset; return 0 < leap && t + 1 == next ? 0 : 1; } offset += leap; } } } int add_leap_seconds(time_t* ptr) { time_t t = *ptr; time_t next = 0; time_t offset = 0; for ( int year = 1970; true; year++ ) { for ( int month = 0; month < 12; month++ ) { time_t seconds = days_in_month(year, month) * 24 * 60 * 60; time_t leap = get_leap_second_maybe(year, month); next += seconds; if ( leap == UNKNOWN ) { *ptr = t + offset; return t < next - 1 ? 1 : -1; } if ( t < next ) { *ptr = t + offset; return leap < 0 && t + 1 == next ? 0 : 1; } offset += leap; } } }