【问题标题】:Calculate time.Time from timestamp starting from 1601-01-01 in Go从 Go 中从 1601-01-01 开始的时间戳计算 time.Time
【发布时间】:2019-09-12 06:58:28
【问题描述】:

我正在尝试解析 .ndx 文件。其中包含一个 64 位值,表示自 1601 年 1 月 1 日 (UTC) 以来的 100 纳秒间隔数。 这是python中的实现:https://github.com/mekh/jtv2xmltv/blob/master/jtv2xml.py#L31

package main

import (
    "fmt"
    "time"
)

func main() {
    var timestamp int64
    timestamp = 132118740000000000

    delta := time.Duration(timestamp)*time.Microsecond
    fmt.Println(delta)

    seconds, _ := divmod(timestamp, 1000000)
    hours, seconds := divmod(seconds, 3600)
    delta = time.Duration(hours)*time.Hour
    fmt.Println(delta)

    layout := "2006-01-02"
    start, _ := time.Parse(layout, "1601-01-01")

    fmt.Println(start.Add(delta))
}

func divmod(numerator, denominator int64) (quotient, remainder int64) {
    quotient = numerator / denominator // integer division, decimals are truncated
    remainder = numerator % denominator
    return
}

https://play.golang.org/p/--zQUtJN5Lh

看起来像 delta 变量溢出,即使按小时设置也是如此。有什么方法可以计算吗?

编辑:见文档https://golang.org/pkg/time/#Duration

Duration 表示两个瞬间之间经过的时间,以 int64 纳秒计数。该表示将最大可表示持续时间限制为大约 290 年。

是否有任何持续时间超过 290 年的第三方包?

EDIT2:最后我需要时间。给定时间戳的时间

【问题讨论】:

  • 对第三方包的请求在这里是题外话。
  • 但一个简单的解决方案可能是存储一个time.Duration,以及一个乘数。例如,如果您需要 3,000 年的期限,则可以存储 30 年的 time.Duration 和 10 倍的乘数。在其他情况下,如果您只关心年数,那么简单的int 就可以工作。是否适合您取决于您​​对持续时间变量执行的操作。
  • 正如您所引用的,time.Duration 不能代表大于 290 年的持续时间。那你想要什么?自给定时间戳的1601-01-01 以来的小时、分钟、秒的值;或代表该时间戳的time.Time 值还是什么?请澄清您的问题。
  • 顺便说一句,您的示例时间戳看起来“很短”,它指定了 1642 年。如果您在其中添加 0:132118740000000000,那么它将是 2019-09-02

标签: datetime go time


【解决方案1】:

time.Duration 是一个int64 值,表示以纳秒为单位的持续时间。如前所述,int64 的最大值约为 290 年,因此无法表示更大的持续时间。

最简单、幼稚的解决方案

一个简单的解决方案是将您的输入转换为time.Duration,这将代表您实际持续时间的百分之一,因为输入以 100 纳秒为单位。您可以将此持续时间添加到从参考日期开始的时间:1601-01-01 UTC 一百次,您就完成了:

func getTime(input int64) time.Time {
    t := time.Date(1601, 1, 1, 0, 0, 0, 0, time.UTC)
    d := time.Duration(input)
    for i := 0; i < 100; i++ {
        t = t.Add(d)
    }
    return t
}

测试它:

fmt.Println(getTime(132118740000000000))

输出(在Go Playground上试试):

2019-09-02 05:00:00 +0000 UTC

朴素解决方案优化

是的,上面的解决方案有一个循环 100 次迭代,这可能不是最优的。

加快上述速度的一种方法是减少迭代次数。如果输入不是“非常”大,我们可能会这样做。例如,如果输入乘以 2 也适合 int64,我们可以将其预乘以 2,然后我们只需要 50 次迭代。同样,如果input*10 也适合int64,我们可以将它预乘以10,然后我们只需要10 次迭代。

输入为 100 纳秒单位。 100 可以被 100, 50, 25, 20, 10, 5, 4, 2 整除,所以为了不损失任何纳秒,我们可以检查这些因素,如果输入乘以这些因素仍然适合int64,如果是这样我们可以将迭代次数除以它。在最好的情况下(如果持续时间小于 2.9 年,我们可以将迭代次数减少到 1)。

这样做的例子:

var divisors = []int64{100, 50, 25, 20, 10, 5, 4, 2}

func getTime(input int64) time.Time {
    iterations := 100
    for _, div := range divisors {
        if input <= math.MaxInt64/div {
            input *= div
            iterations /= int(div)
            break
        }
    }

    t := time.Date(1601, 1, 1, 0, 0, 0, 0, time.UTC)
    d := time.Duration(input)
    for i := 0; i < iterations; i++ {
        t = t.Add(d)
    }
    return t
}

这将输出相同的结果,请在Go Playground 上尝试。在此示例中,迭代次数仅为 2。

最少的迭代次数

与上述解决方案类似,但在每次迭代中,我们将增加可能持续时间最长的时间。即:time.Duration(math.MaxInt64),但由于输入以 100 纳秒为单位,准确地说,我们将使用 time.Duration(math.MaxInt64).Truncate(100 * time.Nanosecond)。我们一直这样做,直到剩余的持续时间小于最大值,这将是获得我们正在寻找的时间瞬间的最后添加。另外,我们也不需要寻找最大除数的第一个循环(可以减少迭代次数)。

func getTime(input int64) time.Time {
    maxd := time.Duration(math.MaxInt64).Truncate(100 * time.Nanosecond)
    maxdUnits := int64(maxd / 100) // number of 100-ns units

    t := time.Date(1601, 1, 1, 0, 0, 0, 0, time.UTC)
    for input > maxdUnits {
        t = t.Add(maxd)
        input -= maxdUnits
    }
    if input != 0 {
        t = t.Add(time.Duration(input * 100))
    }
    return t
}

再次输出是一样的。在Go Playground 上试试这个。

此解决方案可确保最少的迭代次数。例如。如果持续时间少于 290 年,将有一个 time.Add() 调用。如果持续时间在 290 到 580 年之间,将有 2 个time.Add() 调用等。

请注意,在最后的time.Add() 调用中,我们将input 乘以100 以将100 纳秒单位转换为纳秒。这将始终成功,因为只要它大于maxdUnits,之前的循环就会递减它。如果还有一些东西要添加,我们也只调用这个最终的time.Add(),以确保最少的迭代。在实践中,这很可能总是正确的,所以这个if 可以被忽略:即使input 为0,加零也不会改变t,我将它添加到“最少迭代”标题中。

【讨论】:

    【解决方案2】:

    如果您不需要之前的日期,比如 1900 年,为什么不将日期移到 1900 年?

    因此,您从 ndx 中获取 int64,将其从 1601 移到 1900,然后除以 100,然后根据您的新纪元进行计算。没有循环,应该非常快速和准确

    【讨论】:

      【解决方案3】:

      使用 syscall.Filetime 找到另一个解决方案:https://parsiya.net/blog/2018-11-01-windows-filetime-timestamps-and-byte-wrangling-with-go/

      // toTime converts an 8-byte Windows Filetime to time.Time.
      func toTime(t [8]byte) time.Time {
          ft := &syscall.Filetime{
              LowDateTime:  binary.LittleEndian.Uint32(t[:4]),
              HighDateTime: binary.LittleEndian.Uint32(t[4:]),
          }
          return time.Unix(0, ft.Nanoseconds())
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-12-01
        • 2018-08-08
        • 2018-01-24
        • 2018-12-14
        • 1970-01-01
        • 2016-05-06
        相关资源
        最近更新 更多