1 // SPDX-License-Identifier: LGPL-2.0+
3 * Copyright (C) 1993, 1994, 1995, 1996, 1997 Free Software Foundation, Inc.
4 * This file is part of the GNU C Library.
5 * Contributed by Paul Eggert (eggert@twinsun.com).
7 * The GNU C Library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
12 * The GNU C Library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public
18 * License along with the GNU C Library; see the file COPYING.LIB. If not,
19 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
24 * Converts the calendar time to broken-down time representation
27 * Moved from glibc-2.6 to kernel by Zhaolei<zhaolei@cn.fujitsu.com>
29 * Reimplemented by Cassio Neri <cassio.neri@gmail.com>
32 #include <linux/time.h>
33 #include <linux/module.h>
34 #include <linux/kernel.h>
36 #define SECS_PER_HOUR (60 * 60)
37 #define SECS_PER_DAY (SECS_PER_HOUR * 24)
40 * time64_to_tm - converts the calendar time to local broken-down time
42 * @totalsecs: the number of seconds elapsed since 00:00:00 on January 1, 1970,
43 * Coordinated Universal Time (UTC).
44 * @offset: offset seconds adding to totalsecs.
45 * @result: pointer to struct tm variable to receive broken-down time
47 void time64_to_tm(time64_t totalsecs
, int offset
, struct tm
*result
)
49 u32 u32tmp
, day_of_century
, year_of_century
, day_of_year
, month
, day
;
50 u64 u64tmp
, udays
, century
, year
;
51 bool is_Jan_or_Feb
, is_leap_year
;
55 days
= div_s64_rem(totalsecs
, SECS_PER_DAY
, &remainder
);
62 while (rem
>= SECS_PER_DAY
) {
67 result
->tm_hour
= rem
/ SECS_PER_HOUR
;
69 result
->tm_min
= rem
/ 60;
70 result
->tm_sec
= rem
% 60;
72 /* January 1, 1970 was a Thursday. */
73 result
->tm_wday
= (4 + days
) % 7;
74 if (result
->tm_wday
< 0)
78 * The following algorithm is, basically, Proposition 6.3 of Neri
79 * and Schneider [1]. In a few words: it works on the computational
80 * (fictitious) calendar where the year starts in March, month = 2
81 * (*), and finishes in February, month = 13. This calendar is
82 * mathematically convenient because the day of the year does not
83 * depend on whether the year is leap or not. For instance:
85 * March 1st 0-th day of the year;
87 * April 1st 31-st day of the year;
89 * January 1st 306-th day of the year; (Important!)
91 * February 28th 364-th day of the year;
92 * February 29th 365-th day of the year (if it exists).
94 * After having worked out the date in the computational calendar
95 * (using just arithmetics) it's easy to convert it to the
96 * corresponding date in the Gregorian calendar.
98 * [1] "Euclidean Affine Functions and Applications to Calendar
99 * Algorithms". https://arxiv.org/abs/2102.06959
101 * (*) The numbering of months follows tm more closely and thus,
102 * is slightly different from [1].
105 udays
= ((u64
) days
) + 2305843009213814918ULL;
107 u64tmp
= 4 * udays
+ 3;
108 century
= div64_u64_rem(u64tmp
, 146097, &u64tmp
);
109 day_of_century
= (u32
) (u64tmp
/ 4);
111 u32tmp
= 4 * day_of_century
+ 3;
112 u64tmp
= 2939745ULL * u32tmp
;
113 year_of_century
= upper_32_bits(u64tmp
);
114 day_of_year
= lower_32_bits(u64tmp
) / 2939745 / 4;
116 year
= 100 * century
+ year_of_century
;
117 is_leap_year
= year_of_century
? !(year_of_century
% 4) : !(century
% 4);
119 u32tmp
= 2141 * day_of_year
+ 132377;
120 month
= u32tmp
>> 16;
121 day
= ((u16
) u32tmp
) / 2141;
124 * Recall that January 1st is the 306-th day of the year in the
125 * computational (not Gregorian) calendar.
127 is_Jan_or_Feb
= day_of_year
>= 306;
129 /* Convert to the Gregorian calendar and adjust to Unix time. */
130 year
= year
+ is_Jan_or_Feb
- 6313183731940000ULL;
131 month
= is_Jan_or_Feb
? month
- 12 : month
;
133 day_of_year
+= is_Jan_or_Feb
? -306 : 31 + 28 + is_leap_year
;
135 /* Convert to tm's format. */
136 result
->tm_year
= (long) (year
- 1900);
137 result
->tm_mon
= (int) month
;
138 result
->tm_mday
= (int) day
;
139 result
->tm_yday
= (int) day_of_year
;
141 EXPORT_SYMBOL(time64_to_tm
);