用数学方法从Unix时间戳计算日期编号?

15

如何用简单的数学公式,不使用任何函数,从Unix时间戳计算天数。

1313905026 --> 8(今天是08/21/2011)

4个回答

25

一个Unix时间戳不包括闰秒,所以我们不必担心那个。下面是一种无分支1、无循环的算法,用于从Unix时间戳中获取年月日字段:

#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

如果您只对d感兴趣而不是ym,则可以从上述计算中省略最后几行。

该算法在此处进行了详细描述。链接包含完整的推导过程和跨越数百万年的单位测试(这有点过度)。


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

4
感谢您的委托。翻译如下:对于链接背后的详细解释点个赞:http://howardhinnant.github.io/date_algorithms.html#civil_from_days - Semo

6
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。

现在我们已经计算出了年份、月份、日期、小时、分钟和秒数,考虑了闰年的调整。


3
这个常量不比 365.25 更微妙吗? - exebook
如果有人想要了解如何获取小时、分钟和秒格式的数学知识,这是一个不错的解决方案。 - Macintosh Fan

2

没有简单的公式可以做到这一点。您需要减去自纪元以来的年数(考虑闰年),这可能需要使用循环或某种离散计算。然后使用某种类型的循环来减去当前年份每个月的秒数。剩下的就是当前月份的秒数。

我会像这样做。

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
}

未经测试。另外,由于地球并非精确旋转一周需要24小时,所以他们偶尔会对时间进行调整。您需要进行一些研究来考虑这一因素。


谢谢,我认为最好使用日期函数进行计算,因为我想把它放在 SQL 查询的中间。 - user899205
等等,我想到了!稍等一下,我会在上面发布我的答案。 - JSideris

0
在 Rust 中:
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()
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接