V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
airbotgo
V2EX  ›  问与答

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

  •  
  •   airbotgo · 2022-09-12 08:03:47 +08:00 · 2067 次点击
    这是一个创建于 564 天前的主题,其中的信息可能已经有所发展或是发生改变。
    通过函数可以分别计算开始日期到结束日期的已经过去 年数、月数、天数。
    如某事已经过去了 2000 天,如何转化为某事已经过去了“x 年 x 月 x 日”,关键这个“x 日”如何准确计算?

    ( Notion 中碰到的问题)
    第 1 条附言  ·  2022-09-12 09:44:56 +08:00
    样例图片:
    20 条回复    2022-09-13 00:15:56 +08:00
    geelaw
        1
    geelaw  
       2022-09-12 08:26:38 +08:00   ❤️ 3
    这取决于你如何定义“过去了 ... 年 ... 月 ... 日”的概念。

    例如,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
        2
    airbotgo  
    OP
       2022-09-12 09:43:24 +08:00
    @geelaw 感谢如此详尽的回答。这种表述在「倒数日」之类的应用中很常见,展示数据在“过去了 xx 天”和“过去了 x 年 x 月 x 日”之间切换,应该是超过 12 月算 1 年,超过一个月天数算 1 月(具体按每月不同还是固定 30 天,不清楚)
    我也疑惑其中的“x 日”是怎么计算出来的。

    optional
        3
    optional  
       2022-09-12 09:51:21 +08:00
    简单点直接循环累加, 也就循环 x 次而已,x=差值,即使几千年数量级也很低。
    lscho
        4
    lscho  
       2022-09-12 09:52:51 +08:00 via iPhone   ❤️ 1
    按时间戳计算出来的啊,先计算是否大于一年,再计算是否大于 1 月,剩下的就是天。
    airbotgo
        5
    airbotgo  
    OP
       2022-09-12 09:57:37 +08:00
    @optional 这是一个办法。
    @lscho 剩下的天,你具体怎么计算?(每月天数不固定)
    geelaw
        6
    geelaw  
       2022-09-12 12:38:25 +08:00   ❤️ 1
    @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
        7
    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
        8
    HugoChao  
       2022-09-12 12:45:44 +08:00
    技术好贴
    mschultz
        9
    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
        10
    mschultz  
       2022-09-12 13:20:22 +08:00
    mschultz
        11
    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
        12
    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
        13
    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
        14
    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
        15
    yanzhiling2001  
       2022-09-12 14:42:41 +08:00
    先计算是闰年 平年,一年是 365 天还是 366 天,也就是 2 月是 28 天还是 29 天。

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

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

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

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

    我的意思是,我把年月日具体时间写死了,无非就是闰年平年,多出来一天的事。然后往后面计算。
    ql562482472
        17
    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
        18
    wuxkwnjjwoxk  
       2022-09-12 15:55:33 +08:00
    利用现成的日期时间函数不是很简单吗,只要把两个日期转化成标准的 POSIX ,润年那些不用你考虑
    lscho
        19
    lscho  
       2022-09-12 22:51:53 +08:00   ❤️ 1
    @airbotgo 每月天数为什么不能确定? N 天以前,就是以今天为基准啊,之前的所有月份的天数都可以确定的。
    akira
        20
    akira  
       2022-09-13 00:15:56 +08:00
    if 目标日 >= 起始日 then 日 = 目标日 - 起始日
    if 目标日 < 起始日 then 日 = 起始月剩余天数 + 目标日
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5141 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 09:34 · PVG 17:34 · LAX 02:34 · JFK 05:34
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.