summaryrefslogtreecommitdiff
path: root/drivers/rtc/date.c
blob: 1256ffe374ab08efb174125bbddf75ba0c4a02f6 (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
139
140
141
142
// SPDX-License-Identifier: GPL-2.0+
/*
 * (C) Copyright 2001
 * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
 */

#include <common.h>
#include <command.h>
#include <errno.h>
#include <rtc.h>

#if defined(CONFIG_CMD_DATE) || defined(CONFIG_TIMESTAMP)

#define FEBRUARY		2
#define	STARTOFTIME		1970
#define SECDAY			86400L
#define SECYR			(SECDAY * 365)
#define	leapyear(year)		((year) % 4 == 0)
#define	days_in_year(a)		(leapyear(a) ? 366 : 365)
#define	days_in_month(a)	(month_days[(a) - 1])

static int month_days[12] = {
	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

static int month_offset[] = {
	0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};

/*
 * This only works for the Gregorian calendar - i.e. after 1752 (in the UK)
 */
int rtc_calc_weekday(struct rtc_time *tm)
{
	int leaps_to_date;
	int last_year;
	int day;

	if (tm->tm_year < 1753)
		return -1;
	last_year = tm->tm_year - 1;

	/* Number of leap corrections to apply up to end of last year */
	leaps_to_date = last_year / 4 - last_year / 100 + last_year / 400;

	/*
	 * This year is a leap year if it is divisible by 4 except when it is
	 * divisible by 100 unless it is divisible by 400
	 *
	 * e.g. 1904 was a leap year, 1900 was not, 1996 is, and 2000 is.
	 */
	if (tm->tm_year % 4 == 0 &&
	    ((tm->tm_year % 100 != 0) || (tm->tm_year % 400 == 0)) &&
	    tm->tm_mon > 2) {
		/* We are past Feb. 29 in a leap year */
		day = 1;
	} else {
		day = 0;
	}

	day += last_year * 365 + leaps_to_date + month_offset[tm->tm_mon - 1] +
			tm->tm_mday;
	tm->tm_wday = day % 7;

	return 0;
}

int rtc_to_tm(int tim, struct rtc_time *tm)
{
	register int i;
	register long hms, day;

	day = tim / SECDAY;
	hms = tim % SECDAY;

	/* Hours, minutes, seconds are easy */
	tm->tm_hour = hms / 3600;
	tm->tm_min = (hms % 3600) / 60;
	tm->tm_sec = (hms % 3600) % 60;

	/* Number of years in days */
	for (i = STARTOFTIME; day >= days_in_year(i); i++)
		day -= days_in_year(i);
	tm->tm_year = i;

	/* Number of months in days left */
	if (leapyear(tm->tm_year))
		days_in_month(FEBRUARY) = 29;
	for (i = 1; day >= days_in_month(i); i++)
		day -= days_in_month(i);
	days_in_month(FEBRUARY) = 28;
	tm->tm_mon = i;

	/* Days are what is left over (+1) from all that */
	tm->tm_mday = day + 1;

	/* Zero unused fields */
	tm->tm_yday = 0;
	tm->tm_isdst = 0;

	/*
	 * Determine the day of week
	 */
	return rtc_calc_weekday(tm);
}

/*
 * Converts Gregorian date to seconds since 1970-01-01 00:00:00.
 * Assumes input in normal date format, i.e. 1980-12-31 23:59:59
 * => year=1980, mon=12, day=31, hour=23, min=59, sec=59.
 *
 * [For the Julian calendar (which was used in Russia before 1917,
 * Britain & colonies before 1752, anywhere else before 1582,
 * and is still in use by some communities) leave out the
 * -year / 100 + year / 400 terms, and add 10.]
 *
 * This algorithm was first published by Gauss (I think).
 *
 * WARNING: this function will overflow on 2106-02-07 06:28:16 on
 * machines where long is 32-bit! (However, as time_t is signed, we
 * will already get problems at other places on 2038-01-19 03:14:08)
 */
unsigned long rtc_mktime(const struct rtc_time *tm)
{
	int mon = tm->tm_mon;
	int year = tm->tm_year;
	int days, hours;

	mon -= 2;
	if (0 >= (int)mon) {	/* 1..12 -> 11, 12, 1..10 */
		mon += 12;	/* Puts Feb last since it has leap day */
		year -= 1;
	}

	days = (unsigned long)(year / 4 - year / 100 + year / 400 +
			367 * mon / 12 + tm->tm_mday) +
			year * 365 - 719499;
	hours = days * 24 + tm->tm_hour;
	return (hours * 60 + tm->tm_min) * 60 + tm->tm_sec;
}

#endif