| 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 | 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 | days = div_s64_rem(dividend: totalsecs, SECS_PER_DAY, remainder: &remainder); | 
|---|
| 56 | rem = remainder; | 
|---|
| 57 | rem += offset; | 
|---|
| 58 | while (rem < 0) { | 
|---|
| 59 | rem += SECS_PER_DAY; | 
|---|
| 60 | --days; | 
|---|
| 61 | } | 
|---|
| 62 | while (rem >= SECS_PER_DAY) { | 
|---|
| 63 | rem -= SECS_PER_DAY; | 
|---|
| 64 | ++days; | 
|---|
| 65 | } | 
|---|
| 66 |  | 
|---|
| 67 | result->tm_hour = rem / SECS_PER_HOUR; | 
|---|
| 68 | rem %= SECS_PER_HOUR; | 
|---|
| 69 | result->tm_min = rem / 60; | 
|---|
| 70 | result->tm_sec = rem % 60; | 
|---|
| 71 |  | 
|---|
| 72 | /* January 1, 1970 was a Thursday. */ | 
|---|
| 73 | result->tm_wday = (4 + days) % 7; | 
|---|
| 74 | if (result->tm_wday < 0) | 
|---|
| 75 | 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 | udays	= ((u64) days) + 2305843009213814918ULL; | 
|---|
| 106 |  | 
|---|
| 107 | u64tmp		= 4 * udays + 3; | 
|---|
| 108 | century		= div64_u64_rem(dividend: u64tmp, divisor: 146097, remainder: &u64tmp); | 
|---|
| 109 | day_of_century	= (u32) (u64tmp / 4); | 
|---|
| 110 |  | 
|---|
| 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; | 
|---|
| 115 |  | 
|---|
| 116 | year		= 100 * century + year_of_century; | 
|---|
| 117 | is_leap_year	= year_of_century ? !(year_of_century % 4) : !(century % 4); | 
|---|
| 118 |  | 
|---|
| 119 | u32tmp		= 2141 * day_of_year + 132377; | 
|---|
| 120 | month		= u32tmp >> 16; | 
|---|
| 121 | 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 | is_Jan_or_Feb	= day_of_year >= 306; | 
|---|
| 128 |  | 
|---|
| 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; | 
|---|
| 132 | day		= day + 1; | 
|---|
| 133 | day_of_year	+= is_Jan_or_Feb ? -306 : 31 + 28 + is_leap_year; | 
|---|
| 134 |  | 
|---|
| 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; | 
|---|
| 140 | } | 
|---|
| 141 | EXPORT_SYMBOL(time64_to_tm); | 
|---|
| 142 |  | 
|---|