我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Go time包AddDate使用解惑实例详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Go time包AddDate使用解惑实例详解

我们经常会使用 Go time 包 AddDate(),对日期进行计算。而它得到的结果,可能会往往超出我们的“预期”。(为什么预期要打引号,因为我们的预期可能是模糊、偏差的)。

引例

假设,今天是10月31日,是10月的最后一天,我们想通过 AddDate()计算下个月的最后一天。

today := time.Date(2022, 10, 31, 0, 0, 0, 0, time.Local)
nextDay := today.AddDate(0, 1, 0)
fmt.Println(nextDay.Format("20060102"))
// 输出:20221201

结果输出:20221201,而非我们预期的下个月最后一天11月30日。

Go Time 包中是这么处理的

  • AddDate() 对月份+1,即变成了11-31,换算成对应的天数、最终换算成对应的纳秒数存储在 Time 对象中;
  • 输出时,Format()将输出标准的日期,Time 中的纳秒会转为 12-01,而不是 11-31,因为这天并不存在;

只要是涉及到大小月的最后一天都会出现这个问题。

today := time.Date(2022, 3, 31, 0, 0, 0, 0, time.Local)
d := today.AddDate(0, -1, 0)
fmt.Println(d.Format("20060102"))
// 20220303
today := time.Date(2022, 3, 31, 0, 0, 0, 0, time.Local)
d := today.AddDate(0, 1, 0)
fmt.Println(d.Format("20060102"))
// 20220501
today := time.Date(2022, 10, 31, 0, 0, 0, 0, time.Local)
d := today.AddDate(0, -1, 0)
fmt.Println(d.Format("20060102"))
// 20221001
today := time.Date(2022, 10, 31, 0, 0, 0, 0, time.Local)
d := today.AddDate(0, 1, 0)
fmt.Println(d.Format("20060102"))
// 20221201

源码分析

看一下 Go Time 包具体源码,仍以开头10-31 + 1 month的例子为用例。
AddDate(),首先对 month+1,然后调用Date()处理。

// time/time.go
func (t Time) AddDate(years int, months int, days int) Time {
    year, month, day := t.Date() // 获取当前年月日
    hour, min, sec := t.Clock() // 获取当前时分秒
    return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec()), t.Location())
}

Date()中此时传入的参数是

  • year 2020
  • month 11
  • day 31
  • hour、min、sec、nsec 为运行时的时分秒纳秒

d 计算的是绝对纪元到今天之前的天数:

**d = 今年之前的天数 + 年初到当月之前的天数 + 月初到当天之前的天数;**

最终,将 d 转换成纳秒 + 当天经过的纳秒存储在 Time 对象中。

// time/time.go
func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time {
    ……
    // Compute days since the absolute epoch.
    d := daysSinceEpoch(year)
    // Add in days before this month.
    d += uint64(daysBefore[month-1])
    if isLeap(year) && month >= March {
        d++ // February 29
    }
    // Add in days before today.
    d += uint64(day - 1)
    // Add in time elapsed today.
    abs := d * secondsPerDay
    abs += uint64(hour*secondsPerHour + min*secondsPerMinute + sec)
    ……
    return t
}

对 Date() 输入2022-11-31和输入2022-12-01,将得到同样的 d(天数)。两者底层存储的时候都是一样的数据,Format() 时将2022-11-31的Time 格式化成 2022-12-01也就不例外了,输出当然要显示让人看得懂的常规标准日期嘛。

// 2022-11-31
d = 2022年之前的天数 + 1月到10月的总天数 + 30天
// 2022-12-01
d = 2022年之前的天数 + 1月到11月的总天数 + 0天
  = 2022年之前的天数 + 1月到10月的总天数 + 30天 + 0天

你甚至可以往 Date() 输入非标准日期2022-11-35,它和标准日期 2022-12-05,将得到同样的 d (天数)。
“非标准日期”和“标准日期”就像天平的两边,虽然形式不一样,但他们实际的质量(d 天数)是一样的。记住这句话,后面有用。

预期偏差

我们弄清楚了原理,但仍然不能接受这个结果。这样的结果是 Go 的 bug 吗?还是 Go Time 包偷懒了?

然而并不是,恰恰是我们的“预期”出现了问题。

正常来说,我们预期 10-30 + 1 month是 11-30 日,这很合理。那我们为什么还期待 10-31 + 1 month 也是 11-30 日?仅仅因为 10-31是当前月的最后一天,我们也期待 +1 month 后是下个月的最后一天吗?

10-30 和 10-31 两个日期相差一天,进行同样的 +1 month 操作后,就变成为了同一天。这就像 1 + 10 = 2 + 10 一样的结果,这显然不合理。

Go 目前的处理结果是正确的,并且他在 AddDate() 注释中也注明了会处理“溢出”的情况。况且,不止 Go 语言这么处理,PHP 也是这么处理的,见文章令人困惑的strtotime

怎么解决

道理我都懂,但我就是想获取上/下一个月的最后一天怎么办?

利用前面源码分析阶段,提到的“天平原理”,就能拿到我们想要的结果。

today := time.Date(2022, 10, 31, 0, 0, 0, 0, time.Local)
d := today.Day()
// 上个月最后一天
// 10-00 日 等于 9-30 日
day1 := today.AddDate(0, 0, -d)
fmt.Println(day1.Format("20060102"))
// 下个月最后一天
// 12-00 日 等于 11-30 日
day2 := today.AddDate(0, 2, -d)
fmt.Println(day2.Format("20060102"))
// 20220930
// 20221130

结语

最初,发现这个问题是看鸟哥文章,当时认为那是 PHP 的“坑”,并没有深入思考过。如今,在 Go 语言再次遇到这个问题,重新思考,发现日期函数本应该就那么设计,是我们对日期函数理解不够,产生了错误的“预期”。

以上就是Go time包AddDate使用解惑实例详解的详细内容,更多关于Go time包AddDate的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Go time包AddDate使用解惑实例详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Go库text与template包使用示例详解

这篇文章主要为大家介绍了Go库text与template包使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-12-15

使用sessionupload_progress实现文件包含实例详解

这篇文章主要为大家介绍了使用sessionupload_progress实现文件包含实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-12-09

Go WaitGroup的使用方式及实例详解

WaitGroup 是 Go 语言的一个并发控制机制,它可以用于等待一组 goroutine 的结束。WaitGroup 提供了三个方法:Add、Done 和 Wait。1. Add 方法:用于设置 WaitGroup 中等待的 gorou
2023-10-12

go操作Kafka使用示例详解

这篇文章主要为大家介绍了go操作Kafka使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-12-08

C++BoostHeap使用实例详解

Boost是为C++语言标准库提供扩展的一些C++程序库的总称。Boost库是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一,是为C++语言标准库提供扩展的一些C++程序库的总称
2022-11-13

KotlinViewModelProvider.Factory的使用实例详解

这篇文章主要介绍了KotlinViewModelProvider.Factory的使用,在我们使用ViewModel的时候,我们会发现,有的时候我们需要用到ViewModelFactory,有的时候不需要
2023-02-17

Android Studio 修改应用包名实例详解

Android Studio 修改应用包名实例详解 我们平时新建项目有些朋友可能当时就是随意写的一个包名,然后在项目过程中, 又感觉这个包名不太好,所以就要对包名进行修改,根据我们正常的修改方式,是这样的。在种情况是只能修改最外层的那个名称
2022-06-06

go并发利器sync.Once使用示例详解

这篇文章主要为大家介绍了go并发利器sync.Once使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-03-14

go自动下载所有的依赖包go module使用详解

今天在学习dubbo-go的时候,下载了dubbo-go的example,依赖的包太多了,之前都是手动下载某个依赖的包,现在手动一个一个 go get 那太麻烦了。因为我是搞java的,刚开始用go的时候感觉有点奇怪,go代码所依赖的所有的
2022-06-07

Golang中Append()使用实例详解

今天在刷leetcode的时候,第113题让我遇到了一个Go语言中append函数的一个坑,所以复习下,这篇文章主要给大家介绍了关于Golang中Append()使用的相关资料,需要的朋友可以参考下
2023-01-12

vue3 使用defineExpose的实例详解

这篇文章主要介绍了vue3 使用defineExpose的相关知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
2023-03-19

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录