Michael Brown | bd6805a | 2012-03-19 16:09:41 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>. |
| 3 | * |
| 4 | * This program is free software; you can redistribute it and/or |
| 5 | * modify it under the terms of the GNU General Public License as |
| 6 | * published by the Free Software Foundation; either version 2 of the |
| 7 | * License, or any later version. |
| 8 | * |
| 9 | * This program is distributed in the hope that it will be useful, but |
| 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 12 | * General Public License for more details. |
| 13 | * |
| 14 | * You should have received a copy of the GNU General Public License |
| 15 | * along with this program; if not, write to the Free Software |
Michael Brown | c3b4860 | 2012-07-20 19:55:45 +0100 | [diff] [blame] | 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 17 | * 02110-1301, USA. |
Michael Brown | b6ee89f | 2015-03-02 11:54:40 +0000 | [diff] [blame] | 18 | * |
| 19 | * You can also choose to distribute this program under the terms of |
| 20 | * the Unmodified Binary Distribution Licence (as given in the file |
| 21 | * COPYING.UBDL), provided that you have satisfied its requirements. |
Michael Brown | bd6805a | 2012-03-19 16:09:41 +0000 | [diff] [blame] | 22 | */ |
| 23 | |
Michael Brown | b6ee89f | 2015-03-02 11:54:40 +0000 | [diff] [blame] | 24 | FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); |
Michael Brown | bd6805a | 2012-03-19 16:09:41 +0000 | [diff] [blame] | 25 | |
| 26 | #include <time.h> |
| 27 | |
| 28 | /** @file |
| 29 | * |
| 30 | * Date and time |
| 31 | * |
| 32 | * POSIX:2008 section 4.15 defines "seconds since the Epoch" as an |
| 33 | * abstract measure approximating the number of seconds that have |
| 34 | * elapsed since the Epoch, excluding leap seconds. The formula given |
| 35 | * is |
| 36 | * |
| 37 | * tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 + |
| 38 | * (tm_year-70)*31536000 + ((tm_year-69)/4)*86400 - |
| 39 | * ((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400 |
| 40 | * |
| 41 | * This calculation assumes that leap years occur in each year that is |
| 42 | * either divisible by 4 but not divisible by 100, or is divisible by |
| 43 | * 400. |
| 44 | */ |
| 45 | |
Michael Brown | e6111c1 | 2016-06-13 15:29:05 +0100 | [diff] [blame] | 46 | /** Current system clock offset */ |
| 47 | signed long time_offset; |
| 48 | |
Michael Brown | bd6805a | 2012-03-19 16:09:41 +0000 | [diff] [blame] | 49 | /** Days of week (for debugging) */ |
| 50 | static const char *weekdays[] = { |
| 51 | "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" |
| 52 | }; |
| 53 | |
| 54 | /** |
| 55 | * Determine whether or not year is a leap year |
| 56 | * |
| 57 | * @v tm_year Years since 1900 |
| 58 | * @v is_leap_year Year is a leap year |
| 59 | */ |
| 60 | static int is_leap_year ( int tm_year ) { |
| 61 | int leap_year = 0; |
| 62 | |
| 63 | if ( ( tm_year % 4 ) == 0 ) |
| 64 | leap_year = 1; |
| 65 | if ( ( tm_year % 100 ) == 0 ) |
| 66 | leap_year = 0; |
| 67 | if ( ( tm_year % 400 ) == 100 ) |
| 68 | leap_year = 1; |
| 69 | |
| 70 | return leap_year; |
| 71 | } |
| 72 | |
| 73 | /** |
| 74 | * Calculate number of leap years since 1900 |
| 75 | * |
| 76 | * @v tm_year Years since 1900 |
| 77 | * @v num_leap_years Number of leap years |
| 78 | */ |
| 79 | static int leap_years_to_end ( int tm_year ) { |
| 80 | int leap_years = 0; |
| 81 | |
| 82 | leap_years += ( tm_year / 4 ); |
| 83 | leap_years -= ( tm_year / 100 ); |
| 84 | leap_years += ( ( tm_year + 300 ) / 400 ); |
| 85 | |
| 86 | return leap_years; |
| 87 | } |
| 88 | |
| 89 | /** |
| 90 | * Calculate day of week |
| 91 | * |
| 92 | * @v tm_year Years since 1900 |
| 93 | * @v tm_mon Month of year [0,11] |
| 94 | * @v tm_day Day of month [1,31] |
| 95 | */ |
| 96 | static int day_of_week ( int tm_year, int tm_mon, int tm_mday ) { |
| 97 | static const uint8_t offset[12] = |
| 98 | { 1, 4, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5 }; |
| 99 | int pseudo_year = tm_year; |
| 100 | |
| 101 | if ( tm_mon < 2 ) |
| 102 | pseudo_year--; |
| 103 | return ( ( pseudo_year + leap_years_to_end ( pseudo_year ) + |
| 104 | offset[tm_mon] + tm_mday ) % 7 ); |
| 105 | } |
| 106 | |
| 107 | /** Days from start of year until start of months (in non-leap years) */ |
| 108 | static const uint16_t days_to_month_start[] = |
| 109 | { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; |
| 110 | |
| 111 | /** |
| 112 | * Calculate seconds since the Epoch |
| 113 | * |
| 114 | * @v tm Broken-down time |
| 115 | * @ret time Seconds since the Epoch |
| 116 | */ |
| 117 | time_t mktime ( struct tm *tm ) { |
| 118 | int days_since_epoch; |
| 119 | int seconds_since_day; |
| 120 | time_t seconds; |
| 121 | |
| 122 | /* Calculate day of year */ |
| 123 | tm->tm_yday = ( ( tm->tm_mday - 1 ) + |
| 124 | days_to_month_start[ tm->tm_mon ] ); |
| 125 | if ( ( tm->tm_mon >= 2 ) && is_leap_year ( tm->tm_year ) ) |
| 126 | tm->tm_yday++; |
| 127 | |
| 128 | /* Calculate day of week */ |
| 129 | tm->tm_wday = day_of_week ( tm->tm_year, tm->tm_mon, tm->tm_mday ); |
| 130 | |
| 131 | /* Calculate seconds since the Epoch */ |
| 132 | days_since_epoch = ( tm->tm_yday + ( 365 * tm->tm_year ) - 25567 + |
| 133 | leap_years_to_end ( tm->tm_year - 1 ) ); |
| 134 | seconds_since_day = |
| 135 | ( ( ( ( tm->tm_hour * 60 ) + tm->tm_min ) * 60 ) + tm->tm_sec ); |
| 136 | seconds = ( ( ( ( time_t ) days_since_epoch ) * ( ( time_t ) 86400 ) ) + |
| 137 | seconds_since_day ); |
| 138 | |
| 139 | DBGC ( &weekdays, "TIME %04d-%02d-%02d %02d:%02d:%02d => %lld (%s, " |
| 140 | "day %d)\n", ( tm->tm_year + 1900 ), ( tm->tm_mon + 1 ), |
| 141 | tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, seconds, |
| 142 | weekdays[ tm->tm_wday ], tm->tm_yday ); |
| 143 | |
| 144 | return seconds; |
| 145 | } |