【发布时间】:2021-03-12 22:52:39
【问题描述】:
如何在不使用任何函数和简单数学公式的情况下,以数学方式从 unix 时间戳计算天数。
1313905026 --> 8(今天 08/21/2011)
【问题讨论】:
标签: math datetime date unix-timestamp
如何在不使用任何函数和简单数学公式的情况下,以数学方式从 unix 时间戳计算天数。
1313905026 --> 8(今天 08/21/2011)
【问题讨论】:
标签: math datetime date unix-timestamp
unix timestamp 不包括 leap seconds,因此我们不必担心这一点。这是一个无分支1、无循环算法,用于从 unix timestamp 获取 y/m/d 字段:
#include <iostream>
int
main()
{
int s = 1313905026;
int z = s / 86400 + 719468;
int era = (z >= 0 ? z : z - 146096) / 146097;
unsigned doe = static_cast<unsigned>(z - era * 146097);
unsigned yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;
int y = static_cast<int>(yoe) + era * 400;
unsigned doy = doe - (365*yoe + yoe/4 - yoe/100);
unsigned mp = (5*doy + 2)/153;
unsigned d = doy - (153*mp+2)/5 + 1;
unsigned m = mp + (mp < 10 ? 3 : -9);
y += (m <= 2);
std::cout << m << '/' << d << '/' << y << '\n'; // 8/21/2011
}
这个输出:
8/21/2011
由于您对y 和m(仅对d)不感兴趣,您可以从上述计算中消除最后几行。
此算法在here 中被详细描述。该链接包括一个完整的推导,以及跨越数百万年的单元测试(这太过分了)。
1 无分支:上面算法中看起来像小分支的部分在 macOS 上通过 -O3 的 clang 进行了优化:
__Z14get_day_numberi: ## @_Z14get_day_numberi
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
movslq %edi, %rax
imulq $-1037155065, %rax, %rcx ## imm = 0xFFFFFFFFC22E4507
shrq $32, %rcx
addl %ecx, %eax
movl %eax, %ecx
shrl $31, %ecx
sarl $16, %eax
leal (%rax,%rcx), %edx
leal 719468(%rax,%rcx), %esi
testl %esi, %esi
leal 573372(%rax,%rcx), %eax
cmovnsl %esi, %eax
cltq
imulq $963315389, %rax, %rcx ## imm = 0x396B06BD
movq %rcx, %rsi
shrq $63, %rsi
shrq $32, %rcx
sarl $15, %ecx
addl %esi, %ecx
imull $146097, %ecx, %ecx ## imm = 0x23AB1
movl %eax, %esi
subl %ecx, %esi
subl %eax, %esi
leal 719468(%rsi,%rdx), %eax
movl %eax, %ecx
shrl $2, %ecx
imulq $1506180313, %rcx, %rdx ## imm = 0x59C67CD9
shrq $39, %rdx
movl %eax, %esi
subl %edx, %esi
imulq $963321983, %rcx, %rcx ## imm = 0x396B207F
shrq $43, %rcx
addl %esi, %ecx
movl %eax, %edx
shrl $4, %edx
imulq $7525953, %rdx, %rdx ## imm = 0x72D641
shrq $36, %rdx
subl %edx, %ecx
imulq $1729753953, %rcx, %rsi ## imm = 0x6719F361
shrq $32, %rsi
movl %ecx, %r8d
subl %ecx, %eax
movl %ecx, %edi
movl $3855821599, %edx ## imm = 0xE5D32B1F
imulq %rcx, %rdx
subl %esi, %ecx
shrl %ecx
addl %esi, %ecx
shrl $8, %ecx
imull $365, %ecx, %ecx ## imm = 0x16D
subl %ecx, %r8d
shrl $2, %edi
imulq $1506180313, %rdi, %rcx ## imm = 0x59C67CD9
shrq $39, %rcx
shrq $47, %rdx
addl %r8d, %eax
subl %ecx, %eax
leal (%rax,%rdx), %ecx
leal 2(%rcx,%rcx,4), %esi
movl $3593175255, %edi ## imm = 0xD62B80D7
imulq %rsi, %rdi
shrq $39, %rdi
imull $153, %edi, %edi
subl %edi, %esi
leal 4(%rcx,%rcx,4), %ecx
subl %esi, %ecx
movl $3435973837, %esi ## imm = 0xCCCCCCCD
imulq %rcx, %rsi
shrq $34, %rsi
leal 1(%rax,%rdx), %eax
subl %esi, %eax
popq %rbp
retq
.cfi_endproc
【讨论】:
没有简单的公式可以做到这一点。您需要减去自纪元以来的年数(考虑闰年),这可能需要循环或某种离散计算。然后使用某种类型的循环来减去当年每个月的秒数。您剩下的是当前进入该月的秒数。
我会做这样的事情。
x = ...//the number of seconds
year = 1970
while (x > /*one year*/){
x = x - /*seconds in january, and march-december*/
if(year % 4 == 0){
x -= /*leapeay seconds in february*/
}else{
x -= /*regular seconds in february*/
}
}
//Then something like this:
if(x > /*seconds in january*/){
x -= /*seconds in january*/
}
if(x > /*seconds in february*/){
x -= /*seconds in january*/
}
.
.
.
//After that just get the number of days from x seconds and you're set.
编辑
为了简单起见,我建议使用日期函数,但如果有人需要它,或者愿意进一步开发它,这里有一个可能的非循环替代答案。
首先让 t 为自纪元以来的当前时间(以秒为单位)。
设 F 为四年内的秒数。也就是三个平年和一个闰年。那应该是:126230400。
现在如果你把 F 贡献的所有时间都拿走,你会得到一个余数:y。
所以 y = n % F.
现在有几种情况: 1. y 小于一年 2. y 小于两年 3. y小于三年零两个月 4. y 小于三年且大于两个月 5. y 小于四年
请注意,1972 年是闰年,所以如果从 1970 年开始往上数四,那么两年后的任何地方都是闰年。
让 jan, feb, febLY, mar, may, ..., dec 为每个月的秒数(您需要计算出来)。
d 表示当月的天数,D 表示一天中的秒数(86400)。 y代表平年的秒数,yLY代表闰年的秒数。
y = (t % F)
if(y < Y){
if(y > jan){
y -= jan
}
if(y > feb){
y -= feb
}
.
.
.
d = y % D
}
else if(y < 2 * y){
y = y - Y
if(y > jan){
y -= jan
}
if(y > feb){
y -= feb
}
.
.
.
d = y % D
}
else if(y < 2 * y + yLY){
y = y - 2 * Y
if(y > jan){
y -= jan
}
if(y > febLY){
y -= febLY
}
.
.
.
d = y % D
}
else{
y = y - 2 * Y - yLY
if(y > jan){
y -= jan
}
if(y > feb){
y -= feb
}
.
.
.
d = y % D
}
未测试。此外,由于地球不会以完全 1 转/24 小时自转,因此他们偶尔会调整时间。你需要做一些研究。
【讨论】:
t = unix time
second = t MOD 60
minute = INT(t / 60) MOD 60
hour = INT(t / 60 / 60) MOD 24
days = INT(t / 60 / 60 / 24)
years = INT(days / 365.25)
year = 1970 + years + 1
1970 年从星期四开始,所以我们可以计算一周中的哪一天:
weekday = (days + 4) MOD 7
如果星期日是第 0 天。如果您希望星期日是第 1 天,只需添加 1。
现在,让我们看看我们一年中有多少天。
days = days - years * 365 - leapdays
最后,我们找到月份的月份和日期。
IF year MOD 4 = 0 THEN ly = 1 ELSE ly = 0
WHILE month <= 12
month = month + 1
IF month = 2 THEN
DaysInMonth = 28 + NOT(year MOD 4) + NOT(year MOD 100)
+ NOT(year MOD 400)
ELSE
DaysInMonth = 30 + (month + (month < 7)) MOD 2
END IF
IF days > DaysInMonth THEN days = days - DaysInMonth
END WHILE
这假定布尔值 TRUE = 1、FALSE = 0、NOT TRUE = 0 和 NOT FALSE = 1。
现在我们有了根据闰年调整的年、月、日、小时、分钟和秒。
【讨论】:
365.25更微妙吗?
生锈了:
fn date(mut months_to_shift: i32, timezone_shift: i32) -> String {
months_to_shift = months_to_shift * 2_628_000;
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Before time!")
.as_secs() as f32;
let adjusted_time = ((((timestamp / 31_557_600.0) / 4.0).round() as i32 * 86_400)
+ 604800
+ (timezone_shift * 3600)
+ months_to_shift) as f32
+ timestamp; // 608400 offset for number of days missing - 7 - (???) + leap year days +/- timezone shift from EST -- Using timezone shift in my project but not necessary
let years = (1970.0 + (adjusted_time / 31_536_000.0)) as i32;
let mut months = ((adjusted_time % 31_536_000.0) / 2_628_000.0) as i32;
months = if months == 0 { 12 } else { months };
let days = ((adjusted_time % 2_628_000.0) / 86_400.0) as i32;
years.to_string() + "-" + &months.to_string() + "-" + &days.to_string()
}
【讨论】: