【问题标题】:Calculate day number from an unix-timestamp in a math way?以数学方式从unix时间戳计算天数?
【发布时间】:2021-03-12 22:52:39
【问题描述】:

如何在不使用任何函数和简单数学公式的情况下,以数学方式从 unix 时间戳计算天数。

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

【问题讨论】:

    标签: math datetime date unix-timestamp


    【解决方案1】:

    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
    

    由于您对ym(仅对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
    

    【讨论】:

    【解决方案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
    }
    

    未测试。此外,由于地球不会以完全 1 转/24 小时自转,因此他们偶尔会调整时间。你需要做一些研究。

    【讨论】:

    • 谢谢,所以我认为最好使用 DATE 函数来计算它,因为我想把它放在 sql 查询的中间。
    • 等等,我明白了!稍等片刻,我将在上面发布我的答案。
    【解决方案3】:
    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更微妙吗?
    【解决方案4】:

    生锈了:

    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()
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-11-25
      • 1970-01-01
      • 1970-01-01
      • 2015-11-04
      • 2012-08-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多