Line data Source code
1 : // SPDX-License-Identifier: LGPL-2.0+ 2 : /* 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). 6 : * 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. 11 : * 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. 16 : * 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. 21 : */ 22 : 23 : /* 24 : * Converts the calendar time to broken-down time representation 25 : * 26 : * 2009-7-14: 27 : * Moved from glibc-2.6 to kernel by Zhaolei<zhaolei@cn.fujitsu.com> 28 : * 2021-06-02: 29 : * Reimplemented by Cassio Neri <cassio.neri@gmail.com> 30 : */ 31 : 32 : #include <linux/time.h> 33 : #include <linux/module.h> 34 : #include <linux/kernel.h> 35 : 36 : #define SECS_PER_HOUR (60 * 60) 37 : #define SECS_PER_DAY (SECS_PER_HOUR * 24) 38 : 39 : /** 40 : * time64_to_tm - converts the calendar time to local broken-down time 41 : * 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 46 : */ 47 0 : void time64_to_tm(time64_t totalsecs, int offset, struct tm *result) 48 : { 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; 52 : long days, rem; 53 : int remainder; 54 : 55 0 : days = div_s64_rem(totalsecs, SECS_PER_DAY, &remainder); 56 0 : rem = remainder; 57 0 : rem += offset; 58 0 : while (rem < 0) { 59 0 : rem += SECS_PER_DAY; 60 0 : --days; 61 : } 62 0 : while (rem >= SECS_PER_DAY) { 63 0 : rem -= SECS_PER_DAY; 64 0 : ++days; 65 : } 66 : 67 0 : result->tm_hour = rem / SECS_PER_HOUR; 68 0 : rem %= SECS_PER_HOUR; 69 0 : result->tm_min = rem / 60; 70 0 : result->tm_sec = rem % 60; 71 : 72 : /* January 1, 1970 was a Thursday. */ 73 0 : result->tm_wday = (4 + days) % 7; 74 0 : if (result->tm_wday < 0) 75 0 : result->tm_wday += 7; 76 : 77 : /* 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: 84 : * 85 : * March 1st 0-th day of the year; 86 : * ... 87 : * April 1st 31-st day of the year; 88 : * ... 89 : * January 1st 306-th day of the year; (Important!) 90 : * ... 91 : * February 28th 364-th day of the year; 92 : * February 29th 365-th day of the year (if it exists). 93 : * 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. 97 : * 98 : * [1] "Euclidean Affine Functions and Applications to Calendar 99 : * Algorithms". https://arxiv.org/abs/2102.06959 100 : * 101 : * (*) The numbering of months follows tm more closely and thus, 102 : * is slightly different from [1]. 103 : */ 104 : 105 0 : udays = ((u64) days) + 2305843009213814918ULL; 106 : 107 0 : u64tmp = 4 * udays + 3; 108 0 : century = div64_u64_rem(u64tmp, 146097, &u64tmp); 109 0 : day_of_century = (u32) (u64tmp / 4); 110 : 111 0 : u32tmp = 4 * day_of_century + 3; 112 0 : u64tmp = 2939745ULL * u32tmp; 113 0 : year_of_century = upper_32_bits(u64tmp); 114 0 : day_of_year = lower_32_bits(u64tmp) / 2939745 / 4; 115 : 116 0 : year = 100 * century + year_of_century; 117 0 : is_leap_year = year_of_century ? !(year_of_century % 4) : !(century % 4); 118 : 119 0 : u32tmp = 2141 * day_of_year + 132377; 120 0 : month = u32tmp >> 16; 121 0 : day = ((u16) u32tmp) / 2141; 122 : 123 : /* 124 : * Recall that January 1st is the 306-th day of the year in the 125 : * computational (not Gregorian) calendar. 126 : */ 127 0 : is_Jan_or_Feb = day_of_year >= 306; 128 : 129 : /* Convert to the Gregorian calendar and adjust to Unix time. */ 130 0 : year = year + is_Jan_or_Feb - 6313183731940000ULL; 131 0 : month = is_Jan_or_Feb ? month - 12 : month; 132 0 : day = day + 1; 133 0 : day_of_year += is_Jan_or_Feb ? -306 : 31 + 28 + is_leap_year; 134 : 135 : /* Convert to tm's format. */ 136 0 : result->tm_year = (long) (year - 1900); 137 0 : result->tm_mon = (int) month; 138 0 : result->tm_mday = (int) day; 139 0 : result->tm_yday = (int) day_of_year; 140 0 : } 141 : EXPORT_SYMBOL(time64_to_tm);