summaryrefslogtreecommitdiffstats
path: root/qemu/roms/ipxe/src/core/time.c
blob: f70e1981dfc108c13aab3c70af625b31918b16d4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/*
 * Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

FILE_LICENCE ( GPL2_OR_LATER );

#include <time.h>

/** @file
 *
 * Date and time
 *
 * POSIX:2008 section 4.15 defines "seconds since the Epoch" as an
 * abstract measure approximating the number of seconds that have
 * elapsed since the Epoch, excluding leap seconds.  The formula given
 * is
 *
 *    tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 +
 *    (tm_year-70)*31536000 + ((tm_year-69)/4)*86400 -
 *    ((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400
 *
 * This calculation assumes that leap years occur in each year that is
 * either divisible by 4 but not divisible by 100, or is divisible by
 * 400.
 */

/** Days of week (for debugging) */
static const char *weekdays[] = {
	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};

/**
 * Determine whether or not year is a leap year
 *
 * @v tm_year		Years since 1900
 * @v is_leap_year	Year is a leap year
 */
static int is_leap_year ( int tm_year ) {
	int leap_year = 0;

	if ( ( tm_year % 4 ) == 0 )
		leap_year = 1;
	if ( ( tm_year % 100 ) == 0 )
		leap_year = 0;
	if ( ( tm_year % 400 ) == 100 )
		leap_year = 1;

	return leap_year;
}

/**
 * Calculate number of leap years since 1900
 *
 * @v tm_year		Years since 1900
 * @v num_leap_years	Number of leap years
 */
static int leap_years_to_end ( int tm_year ) {
	int leap_years = 0;

	leap_years += ( tm_year / 4 );
	leap_years -= ( tm_year / 100 );
	leap_years += ( ( tm_year + 300 ) / 400 );

	return leap_years;
}

/**
 * Calculate day of week
 *
 * @v tm_year		Years since 1900
 * @v tm_mon		Month of year [0,11]
 * @v tm_day		Day of month [1,31]
 */
static int day_of_week ( int tm_year, int tm_mon, int tm_mday ) {
	static const uint8_t offset[12] =
		{ 1, 4, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5 };
	int pseudo_year = tm_year;

	if ( tm_mon < 2 )
		pseudo_year--;
	return ( ( pseudo_year + leap_years_to_end ( pseudo_year ) +
		   offset[tm_mon] + tm_mday ) % 7 );
}

/** Days from start of year until start of months (in non-leap years) */
static const uint16_t days_to_month_start[] =
	{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };

/**
 * Calculate seconds since the Epoch
 *
 * @v tm		Broken-down time
 * @ret time		Seconds since the Epoch
 */
time_t mktime ( struct tm *tm ) {
	int days_since_epoch;
	int seconds_since_day;
	time_t seconds;

	/* Calculate day of year */
	tm->tm_yday = ( ( tm->tm_mday - 1 ) +
			days_to_month_start[ tm->tm_mon ] );
	if ( ( tm->tm_mon >= 2 ) && is_leap_year ( tm->tm_year ) )
		tm->tm_yday++;

	/* Calculate day of week */
	tm->tm_wday = day_of_week ( tm->tm_year, tm->tm_mon, tm->tm_mday );

	/* Calculate seconds since the Epoch */
	days_since_epoch = ( tm->tm_yday + ( 365 * tm->tm_year ) - 25567 +
			     leap_years_to_end ( tm->tm_year - 1 ) );
	seconds_since_day =
		( ( ( ( tm->tm_hour * 60 ) + tm->tm_min ) * 60 ) + tm->tm_sec );
	seconds = ( ( ( ( time_t ) days_since_epoch ) * ( ( time_t ) 86400 ) ) +
		    seconds_since_day );

	DBGC ( &weekdays, "TIME %04d-%02d-%02d %02d:%02d:%02d => %lld (%s, "
	       "day %d)\n", ( tm->tm_year + 1900 ), ( tm->tm_mon + 1 ),
	       tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, seconds,
	       weekdays[ tm->tm_wday ], tm->tm_yday );

	return seconds;
}