编程中关于日期结束的争议

问题

我们做一个选择题,今天的结束时间是()。
A. 23:59:59
B. 23:59:59.999
C. 24:00:00
D. 00:00:00(第二天的零时)

问题由来

这编程过程中,经常需要查询某一个时间段内的数据,特别是某一天或某几天,这时候就存在两个值,一个是起始值,一个是结束值。

假如我们获取的是今天一整天的数据,那么起始值应当是00:00:00,这个一般不存在异议。但是在结束值的问题上则存在争议了,大多数人认为结束值应该是23:59:59,或者在这个基础上再加上毫秒值。

随便这百度上搜索出相关的答案也大多都认为是23:59:59

答案

这道选择题应该选择的是CD。
C和D其实是一样的,这个我不做解释。
我知道很多人觉得我说得不对,C和D明明是第二天的开始,怎么会是第一天的结束呢?

这句话前半句对了,确实是第二天的开始,但它们也是第一天的结束。

证明

纠结的等式

我们来做个小学数学题:

1 ÷ 3 = 1/3 --------------------- ①
1 ÷ 3 = 0.333… --------------- ②(我以3个连续的“…”表示无限循环,下同)
---------------------已知①式与②式左边相等,同时,右边也相等;
1/3 × 3 = 1 --------------------- ③
0.333… × 3 = 0.999… ------ ④
---------------------那么③式与④式右边相等吗?

这个问题在我初中时第一次遇到,当时的想法自然是认为1与0.999…不等的,但这个推理过程确实无懈可击的,那么问题出在哪里呢?答案就是1与0.999…完全相等。

果壳网的证明

若是不信可以去看2011年果壳网发表的文章最让人纠结的等式:0.999…=1,怀念初次看到这篇文章的岁月。

文章里其实已经说了,上面这个问题本身就是对这个结论的证明。如果不想看枯燥的数学证明,那么看看这篇文章下面的评论也是可以的。
评论

  • 热评第一的意思就是:如果你认为1与0.999…不等,由于数是无穷的,那么1与0.999…之间必然存在一个数,介于两个数之间。但是并没有人能找出这个数,所以这两个数就是相等的。

  • 热评第二就是很多人会说的,两个数之间总差了“1”,我们可以计算一下:

1 - 0.999… = 0.000…
这两个数想减的结果是无穷个0,并没有1出现,所以结果是0,所以两数相等。

那么我们如果要寻找所有0~1之间的数应该是什么样的呢?

(0,1)
即所有大于0小于1的数,等价于(0,0.999…)

结束时间的证明

数学证明

所以,我们要找今天一整天的时间范围就应该是从昨天结束,到明天开始这之间的所有时间。
按照大多数人的想法,应该是00:00:0023:59:59之间,但是由于精度问题,所以更准确的表达应该是00:00:0023:59:59.999…

上面我们已经知道:

1 = 0.999…且(0,1)等价于(0,0.999…)
同样的,23:59:59.999…等价于24:00:00,而24:00:00等价于00:00:00
所以,23:59:59.999…等价于00:00:00
所以,一天的时间范围应该是00:00:00至第二天的00:00:00

代码证明

如果上面的数学推理还不能让你信服,那么我们用代码来证明:

public static void main(String[] args) throws ParseException {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		Date start = sdf.parse("2019-06-27 00:00:00");
		Date end = sdf.parse("2019-06-27 23:59:59");
		System.out.println("今天一天的秒数:" + (end.getTime() - start.getTime()) / 1000);
	}

运行结果:
结果
众所周知,一天的秒数 = 60 * 60 * 24 = 86400,然而运行结果少了一秒,难道这一秒被续了不成?

可能有人说精度不对,那么把上面的代码稍做修改:

public static void main(String[] args) throws ParseException {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
		Date start = sdf.parse("2019-06-27 00:00:00.000");
		Date end = sdf.parse("2019-06-27 23:59:59.999");
		System.out.println("今天一天的毫秒数:" + (end.getTime() - start.getTime()));
	}

运行结果:
结果
呃。。。还是有1毫秒被续了,这是什么情况?

即使继续提高精度,还是不会得到完整的86400,除非无限地提高精度,那么无限提高精度不就回到了刚才的1与0.999…的问题了么?

所以结论就是一天的完整时间应该是00:00:00至第二天的00:00:00

证明完毕!

最后我要吐槽一下,目前和我讨论过这个问题的人基本都认为应该是23:59:59,包括国内某个我很喜欢的著名的工具库的代码也是这样写的,但是瑕不掩瑜啦,都是小事情。不过作为很严肃的数学问题,我还是要写这样一篇文章才行。

JDK源码

在浏览JDK源码的时候,无意中在java.time.LocalTime类中看到这样一段代码:

static {
        for (int i = 0; i < HOURS.length; i++) {
            HOURS[i] = new LocalTime(i, 0, 0, 0);
        }
        MIDNIGHT = HOURS[0];
        NOON = HOURS[12];
        MIN = HOURS[0];
        MAX = new LocalTime(23, 59, 59, 999_999_999);
    }

这里的MAX的取值并非00:00:00,我分析了一下原因。

从代码中的9个9,我们可以看出其实这里也是在逼近临界值。为什么只是逼近而不是直接用临界值呢?因为这是java.time.LocalTime类,其中只包含时间信息,不包含日期信息,所以一旦达到临界值,就溢出了,由MAX变成MIN了。

所以这里只能是一个比临界值小的值,也就是说与我前面的证明并不矛盾。

这里有一处不明白的地方是MINMIDNIGHT有什么区别吗?希望有大佬解答

/**
 * The minimum supported {@code LocalTime}, '00:00'.
 * This is the time of midnight at the start of the day.
 */
public static final LocalTime MIN;
/**
 * The time of midnight at the start of the day, '00:00'.
 */
public static final LocalTime MIDNIGHT;
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值