如何准确转化年月日的时间?

2022-09-12 08:03:47 +08:00
 airbotgo
通过函数可以分别计算开始日期到结束日期的已经过去 年数、月数、天数。
如某事已经过去了 2000 天,如何转化为某事已经过去了“x 年 x 月 x 日”,关键这个“x 日”如何准确计算?

( Notion 中碰到的问题)
2084 次点击
所在节点    问与答
20 条回复
geelaw
2022-09-12 08:26:38 +08:00
这取决于你如何定义“过去了 ... 年 ... 月 ... 日”的概念。

例如,2001 年 3 月 1 日是 2000 年 2 月 29 日之后的多少年多少月多少日?

我个人认为无歧义的表达是:
0 年 11 月 31 日
0 年 10 月 62 日
0 年 9 月 92 日
0 年 8 月 123 日
0 年 7 月 153 日
0 年 6 月 184 日
0 年 5 月 215 日
0 年 4 月 245 日
0 年 3 月 276 日
0 年 2 月 306 日
0 年 1 月 337 日
0 年 0 月 366 日

不可以说 2000 年 2 月 29 日的 1 年 0 月 ? 天之后,因为不存在 2001 年 2 月 29 日。

形式化来说,我定义“a 年 b 月 c 日的 x 年 y 月 z 日之后的那一天”的概念存在,当且仅当 (a+x+floor((b-1+y)/12)) 年 1+(b-1+y)%12 月 c 日存在,且这个概念表示的是 (a+x+floor((b-1+y)/12)) 年 1+(b-1+y)%12 月 c 日之后的第 z 日。

换言之,增加 x 年 y 月 z 日的意思是前进 (12x+y) 个月并保持“日”不变(假设这一天存在),然后再前进 z 日,只有年月之间是可以自由转换的,年月和日之间的转换比较复杂。
airbotgo
2022-09-12 09:43:24 +08:00
@geelaw 感谢如此详尽的回答。这种表述在「倒数日」之类的应用中很常见,展示数据在“过去了 xx 天”和“过去了 x 年 x 月 x 日”之间切换,应该是超过 12 月算 1 年,超过一个月天数算 1 月(具体按每月不同还是固定 30 天,不清楚)
我也疑惑其中的“x 日”是怎么计算出来的。

optional
2022-09-12 09:51:21 +08:00
简单点直接循环累加, 也就循环 x 次而已,x=差值,即使几千年数量级也很低。
lscho
2022-09-12 09:52:51 +08:00
按时间戳计算出来的啊,先计算是否大于一年,再计算是否大于 1 月,剩下的就是天。
airbotgo
2022-09-12 09:57:37 +08:00
@optional 这是一个办法。
@lscho 剩下的天,你具体怎么计算?(每月天数不固定)
geelaw
2022-09-12 12:38:25 +08:00
@airbotgo #2 你似乎没有理解 #1 的用意。

> 如某事已经过去了 2000 天,如何转化为某事已经过去了“x 年 x 月 x 日”,关键这个“x 日”如何准确计算?

#1 的定义表明从 2000 天无法算出多少年多少月多少日,例如:

2000 年 8 月 31 日是 2000 年 7 月 31 日之后 31 日,也是它之后 0 年 1 月 0 日。
2000 年 10 月 1 日是 2000 年 8 月 31 日之后 31 日,但不是它之后 0 年 1 月 0 日。

同样是 31 日,不能得到它到底是不是 0 年 1 月 0 日。因此问题不成立,但如果你知道开始和结束的日子,则很容易根据定义计算到底是多少年多少月多少日,同理,如果一个软件采用了 #1 的定义,那么它并不是先算出多少日,再仅从多少日转换为多少年多少月多少日的,而是直接算出来。

如果你想问某个软件是如何计算多少年多少月多少日的,最好的方法是直接去看代码,毕竟不同的人定义不同。

如果你想问 #1 的定义下的最佳表达(年数最大的基础上月数最大)如何计算,下面是一种方法:

计算 a 年 b 月 c 日是 x 年 y 月 z 日之后的 u 年 v 月 w 日。假设 a b c d e f > 0 且不考虑历法变更而不存在的日子,这些数表达了存在的日子,且 x 年 y 月 z 日不早于 a 年 b 月 c 日。

https://gist.github.com/GeeLaw/9c68befab1b125a33c52deaf386bf92a
geelaw
2022-09-12 12:42:24 +08:00
@geelaw #7

>假设 a b c d e f > 0 且不考虑历法变更而不存在的日子,这些数表达了存在的日子,且 x 年 y 月 z 日不早于 a 年 b 月 c 日。

更正为

>假设 a b c x y z > 0 且不考虑历法变更而不存在的日子,这些数表达了存在的日子,且 x 年 y 月 z 日不晚于 a 年 b 月 c 日。
HugoChao
2022-09-12 12:45:44 +08:00
技术好贴
mschultz
2022-09-12 13:04:55 +08:00
根据 GPS 和 /或天文学中的习惯,可以先把格里高利历的日期转换为儒略日( JD )或简化儒略日( MJD ),然后计算日期差:

https://en.wikipedia.org/wiki/Julian_day#Converting_Gregorian_calendar_date_to_Julian_Day_Number
mschultz
2022-09-12 13:20:22 +08:00
mschultz
2022-09-12 13:35:43 +08:00
@mschultz #9 好像距离解决本帖的原始问题还差一步。

与 JD 之间的转换能准确解决 「 A 年 B 月 C 日 的 X 天后 是 A' 年 B' 月 C' 日」的计算问题,但这个差值如何以年月日表达确实还需要 #1 #6 这样的讨论。

最简单的例子,是否可以直接说 「相差 (A'-A) 年 (B'-B) 月 (C'-C) 天」?

ISO8601 - Time interval 中似乎没有对 "1M" "1Y" 这样的 Interval 到底包含多少天作出强制规定:

https://stackoverflow.com/questions/33123582/how-many-days-in-iso8601-duration-months-and-years
aureole999
2022-09-12 13:37:40 +08:00
一般中文表达只有过去了几年几个月,很少有精确到日子的。日期比较接近时,比如 2020-03-05 和 2022-04-06 可能会说过去了 2 年 1 个月零 1 天,但如果是 2022-04-04 ,只会说差 1 天就 2 年 1 个月了,不会说 2 年 0 月 29/30 天,因为没有人定义这个天是怎么计算的。

我猜 Notion 是先把年月计算出来,然后只看日数,如果现在日数大于开始日数,直接减。如果小于,就用上个月的总天数减去日数差,再把月数-1 。其实更合理一点应该是在日期小于时就用差几天来表示。
yanzhiling2001
2022-09-12 14:20:47 +08:00
楼主是倒数日的开发者吗,我曾经也试着写过类似倒数日这样的应用。种种原因半途而废了。

这个功能 /问题我也考虑过,我当时的写法的是 把(闰 /平)年月日,具体天数写死了。

1 3 5 7 8 10 12 月,都是 31 天,余下除了 2 月都是 30 天,2 月根据闰平年判断是 28 还是 29 天。

===========================================

假如说今天 9 月 12 号,计算距离今天 100 天是几月几号:

距离九月还有 18 天,100 -18 =82 天,

82 天 又可以拆为 31 天( 10 月 )+ 30 天( 11 月)+ 21 天(余下不足三十天的,就是几号),就是 12 月 21 号。

9 月 12 号,计算距离今天 100 天,是 12 月 21 号

=============================================

都是写死的,这种就是个类似穷举的蠢办法,当然也能用。

如果楼主有更精妙的算法 麻烦跟我说一下。
yanzhiling2001
2022-09-12 14:34:31 +08:00
假如说今天 2022 年 9 月 12 号,计算距离今天 2000 天是几月几号:

这不更好计算了。


2022 年余下 109 天,

2023 年是平年,共有 365 天
2024 年是闰年,共有 366 天
2025 年是平年,共有 365 天
2026 年是平年,共有 365 天
2027 年是平年,共有 365 天

2028 年是闰年,共有 366 天

2000-109-365*4-366=66 天

也就说是 2028 年的第 65 天,就是距今 2000 天,2028 年 1 月 31 天,2 月 29 天,65-31(2028 年 1 月) - 29(2028 年 2 月)=5

就是 2028 年的 3 月 5 日?
yanzhiling2001
2022-09-12 14:42:41 +08:00
先计算是闰年 平年,一年是 365 天还是 366 天,也就是 2 月是 28 天还是 29 天。

然后往后面推算是多少天,

小于 365 天的日期,只看 2 月是不是闰月,然后配合大小月,算 31 天还是 31 天

大约 365 的日期,往后整除取余算约有几年,之间有没有闰年,闰年-366 平年 -365

办法很蠢,不过很有效。
yanzhiling2001
2022-09-12 14:46:18 +08:00
好吧,我表达水平堪忧,楼主仅供参考。

我的意思是,我把年月日具体时间写死了,无非就是闰年平年,多出来一天的事。然后往后面计算。
ql562482472
2022-09-12 15:52:42 +08:00
LocalDate.now().minusDays(2000)
//2017-03-22

Period.between(LocalDate.now(),LocalDate.of(2017,3,22))
//P5Y5M21D

Java 的定义是这样的 :
Obtains a Period consisting of the number of years, months, and days between two dates.
The start date is included, but the end date is not. The period is calculated by removing complete months, then calculating the remaining number of days, adjusting to ensure that both have the same sign. The number of months is then split into years and months based on a 12 month year. A month is considered if the end day-of-month is greater than or equal to the start day-of-month. For example, from 2010-01-15 to 2011-03-18 is one year, two months and three days.
The result of this method can be a negative period if the end is before the start. The negative sign will be the same in each of year, month and day.
wuxkwnjjwoxk
2022-09-12 15:55:33 +08:00
利用现成的日期时间函数不是很简单吗,只要把两个日期转化成标准的 POSIX ,润年那些不用你考虑
lscho
2022-09-12 22:51:53 +08:00
@airbotgo 每月天数为什么不能确定? N 天以前,就是以今天为基准啊,之前的所有月份的天数都可以确定的。
akira
2022-09-13 00:15:56 +08:00
if 目标日 >= 起始日 then 日 = 目标日 - 起始日
if 目标日 < 起始日 then 日 = 起始月剩余天数 + 目标日

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/879372

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX