Board logo

标题: [原创] 批处理中的时间计算详解 [打印本页]

作者: batman    时间: 2009-8-20 13:12     标题: 批处理中的时间计算详解

  最近总是有人在询问涉及到时间和日期计算的问题,其实要计算时间和日期最有效的方
法是用vbs的时间函数,几行代码就解决了。但我们今天要谈的就是如何用批来计算时间和日期
(有点自找苦吃的感觉),下面就展开正题吧:

  一、系统中的date和time变量
  要进行时间和日期的计算,我们必须认识两个系统变量date和time。当你在cmd中输入ec
ho %date%#%time%再回车,屏幕上就会出现这样的一排字符:2009-08-19 星期三#10:20:10.20
,前面是表示的当前的日期-2009年8月19日星期三共14个字符(中间有个空格),后面表示的是当
前的时间10点20分10秒20毫秒共13个字符。当然在不同的系统中时间格式是不一样的,我们暂以
xp系统为例来继续后面的分析。转回正题,现在我们可以通过变量截取命令来获得想要的时间参
数。如我想要获得当前的小时和分的值并把它分别赋值给名为h和m的变量:set "h=%time:~,2%
"&set "m=%time:~3,2%",在这里要注意除了年以外所有的时间参数都是两位数的格式(年是四
位),当数值不满两位时,系统会自动在前面补上0,小时是补上空格如: 8:04:09:03,这就会给我
们的计算带来麻烦(小时中的空格不影响计算),具体的将会在后面详细说明。

  二、进制互换和去零操作
  事实上时间参数是很麻烦,其参数格式上存在着数种进制:十进制(年、毫秒)、十二进制(
月)、三十进制(天,姑且这么说)、七进制(星期)、二四进制(小时)、六十进(分秒)。大家
看是不是很麻烦,因为cmd中只能进行十进制计算,所以我们就必须先将所有的要计算的参数转化成
十进制计算后再转回相应的进制。我们前面讲到当分、秒(还有月、天)值不满两位时,系统会自动在前
面补上0,要想正确计算,我们就必须去掉前面这个0。为什么?因为在cmd中不会将08 09这样的数值
认定为是十进制数值,而cmd中一般是只能进行十进制计算的(除了位运算),所以会导致程序出错。
那么如何进行进制互换以及去零操作呢?下面我们以例示之并说明:
  1. @echo off&setlocal enabledelayedexpansion
  2. rem 计算当前时间往后1000秒是什么时间(不包含日期计算)
  3. set "h=%time:~,2%"&set "m=%time:~3,2%"&set "s=%time:~6,2%"
  4. echo 当前时间%h%:%m%:%s%
  5. rem 上面依次截取时间变量的时、分、秒值并分别赋值给h、m、s变量
  6. for /l %%a in (1,1,1000) do (
  7.      set /a m=1!m!%%100,s=1!s!%%100,s+=1
  8.      rem 上面将m、s变量去零转化为有效的10进制数值
  9.      if !s! equ 60 set /a s=0,m+=1
  10.      if !m! equ 60 set /a m=0,h+=1
  11.      if !h! equ 24 set /a h=0
  12.      rem 上面将每次递加后的时、分、秒值转化为相应的进制
  13. )
  14. set "m=0%m%"&set "m=!m:~-2!"
  15. set "s=0%s%"&set "s=!s:~-2!"
  16. rem 上面将分、秒值在前面补0并取后两位数(补足两位数)
  17. echo 往后1000秒是%h%:%m%:%s%
  18. pause>nul
复制代码
  这时,可能有人会问到上面的去零究竟是怎么实现的?其实关于这个我在《批处理实用技
术》一贴中就已经说明了,今天就再讲解一次。首先大家要认识set /a 中这个命令符"%%",它表
示取余操作,就是我们数学中所说的取被除数相对于除数的余数。如:set /a a=14%%3,b=27%%3
a b的值分别为2和0,也就是说14/3=4(2)余数是2,27/3=9(0)余数是0。那么我们现在回到时间
的计算上来,假设当前时间为7:04:08秒,我们就可以在分和秒的值前都加上1,然后对100取余,
余数不就是4和8了吗?我们还是用代码来加以说明:
  1. @echo off
  2. set "times= 7:04:08"&rem 在设置变量是不要与系统变量同名,所以加上了s
  3. set /a h=%times:~,2%,m=1%times:~3,2%%%100,s=1%times:~6,2%%%100
  4. echo %h%:%m%:%s%
  5. pause>nul
复制代码
  三、复杂的时间计算回溯和推移
  这一问题多出现对一定时间范围内的文件操作上,要确定这一时间范围就必须进行时间
的计算。首先我们来看一段代码:
  1. @echo off&setlocal enabledelayedexpansion
  2. rem 计算当前日期时间回溯3286小时的日期和时间
  3. set /a y=%date:~,4%,mo=1%date:~5,2%%%100,d=1%date:~8,2%%%100,h=%time:~,2%,m=1%time:~3,2%%%100,s=1%time:~6,2%%%100
  4. echo 当前时间为:%y%年%mo%月%d%日%h%时%m%分%s%秒
  5. call :judge
  6. for %%a in (1 3 5 7 8 10 12) do set "_%%a=31"
  7. for %%a in (4 6 9 11) do set "_%%a=30"
  8. set /a num_d=3286/24,num_h=3286%%24
  9. for /l %%a in (1,1,%num_d%) do (
  10.      set /a d-=1
  11.      if !d! equ 0 call :lp
  12. )
  13. for /l %%a in (1,1,%num_h%) do (
  14.      set /a h-=1
  15.      if !h! equ 0 set /a h=24,d-=1
  16.      if !d! equ 0 call :lp
  17. )
  18. echo 回溯3286小时是%y%年%mo%月%d%日%h%时%m%分%s%秒
  19. pause>nul&goto :eof
  20. :lp
  21. set /a mo-=1
  22. if !mo! equ 0 set /a mo=12,y-=1&call :judge
  23. set /a d=_%mo%&goto :eof
  24. :judge
  25. rem 对闰年的判断并设定2月天数
  26. set /a a=%y%%%4,b=%y%%%100,c=%y%%%400,_2=28
  27. if %b% equ 0 (
  28.   if %c% equ 0 set /a _2+=1
  29.   ) else (
  30.   if %a% equ 0 set /a _2+=1
  31. )  
复制代码
  现在我们来对这段代码进行详解。大家知道,在时间上有一个重要的不定因素:二月的总
天数,当年份为闰年时二月的总天数为29天,否则就是28天。那么当时间回溯和推移时这个对闰
年的判断是显得极为重要,那么我们怎么判断闰年呢?当然是根据闰年的定义来判断:能被100整
除且能整除400以及不能被100整除但能被4整除的年份都是闰年,反之则是平年。如代码中的ju
dge子块就是对计算过程中各到达年份(当月份为12月时)是否为闰年。那么除了这个二月的总天
数,一年中其他各月的总天数也存在着两个值:一、三、五、七、八、十、十二月为大月总天数是31天
,四、六、九、十一月为小月总天数为30天。在代码中就用两个for对各月的天数进行了设定(如前
所述二月是动态变化的)。当计算过程中各到达月份为几月时(当天数递减到0时就将月份值减一
),就将天数值设定为月份的最后一天,如月份的值为9,则设定为30天,然后再继续递减。大家可
以看到代码中对回溯小时数进行了换算,换算成为了回溯多少天零多少个小时,这是为了减少循
环次数提高程序运行的效率。这是大家可能又会有疑问,既然是这样,为什么不再一步换算成多
少年多少月多少天多少小时呢?从通用性上来讲,我可以告诉大家这是不行的。因为年的总天数
和月的总天数是存在变化的,是不定值,当然不能进行换算了。

  现在我们来从头理一下思路:上述代码先是获取到当前时间和各级参数并赋值给变量(分
和秒没有参与运算过程),然后对当前的年份进行闰年判断设定好2月的初始天数,接下来对一年
中各月的天数进行设定,这时我们为了提高效率将回溯时间换算成了回溯多少天多少小时的模
式并分别赋值给相应计数变量,再通过两个for循环进行按天递减和按小时递减的计算,在这其
中当到达三个临界点:小时值为零、天数值为零、月份值为零时,分别进行了退位、重赋值和判断
(重点在于对2月天数的重设定),最后输出结果。今天我们所举的例子是对回溯时间的计算,时间
推移计算的方法也是一样,只是递减变成了递增以及临界值发生了改变。

  好了,本文洋洋洒洒一大版,无非就是想将时间计算的原理、方法及注意事项告之于大家
,至于写代码还要具体问题具体分析。只要大家掌握了基础的东西,相信任何时间的计算问题
对你们来说都会是小菜一碟了。

[ 本帖最后由 batman 于 2009-8-20 22:00 编辑 ]
作者: 5566ljlj    时间: 2009-10-27 15:21

学习并支持楼主!如果使用"set /p a="句式的话,那就可以计算输入的任意差值了。
作者: vincentzhou    时间: 2010-12-29 14:52     标题: 在运行以下代码的时候遇到了bug 但是不知道什么原因

发现运行过程中有bug,就把例子改了一下得到的结果在下面,很明显当秒进位使分加一的时候,秒多了十秒,并且还持续到了08秒
  1. @echo off&setlocal enabledelayedexpansion
  2. :start
  3. rem 计算当前时间往后1000秒是什么时间(不包含日期计算)
  4. set "h=%time:~,2%"&set "m=%time:~3,2%"&set "s=%time:~6,2%"
  5. echo 当前时间%h%:%m%:%s%
  6. rem 上面依次截取时间变量的时、分、秒值并分别赋值给h、m、s变量
  7. for /l %%a in (1,1,10) do (
  8.      set /a m=1!m!%%100,s=1!s!%%100,s+=1
  9.      rem 上面将m、s变量去零转化为有效的10进制数值
  10.      if !s! equ 60 set /a s=0,m+=1
  11.      if !m! equ 60 set /a m=0,h+=1
  12.      if !h! equ 24 set /a h=0
  13.      rem 上面将每次递加后的时、分、秒值转化为相应的进制
  14. )
  15. set "m=0%m%"&set "m=!m:~-2!"
  16. set "s=0%s%"&set "s=!s:~-2!"
  17. rem 上面将分、秒值在前面补0并取后两位数(补足两位数)
  18. echo 往后10秒是%h%:%m%:%s%
  19. pause>nul
  20. goto start
复制代码
当前时间14:38:59
往后10秒是14:39:19
当前时间14:39:02
往后10秒是14:39:22
当前时间14:39:07
往后10秒是14:39:27
当前时间14:39:08
往后10秒是14:39:28
当前时间14:39:09
往后10秒是14:39:19
作者: hanyeguxing    时间: 2010-12-29 15:42     标题: 回复 3楼 的帖子

问题出在 set /a m=1!m!%%100,s=1!s!%%100,s+=1 上
假设s=05,那么for /l第一次循环时,s=105%%100,得到的是5。然后加1,得到6
第二次循环时s=16%%100,直接跳为16。。。以后就这样错下去。
所以 set /a m=1!m!%%100,s=1!s!%%100,s+=1 这样的方法只能用一次,不能放到多次循环内。。。
作者: vincentzhou    时间: 2010-12-29 16:32

的确 将set /a m=1!m!%%100,s=1!s!%%100 放到循环之外问题就解决了 谢谢寒夜孤星
作者: wc726842270    时间: 2011-2-17 04:12

顶一下,以前没看到,很不错
作者: mstsc    时间: 2011-6-26 23:35

学习进步中  慢····慢··········慢·····················
作者: zdzjs    时间: 2011-7-20 21:15

对我非常有用,我正需要在批处理中计算前多少小时的代码,用了你的代码后,当系统时间为1-23时都能正确下去,但小时数为0时时,计算不出前多少小时之前的时间,请问怎么修改才能正确计算?非常感谢!
作者: Batcher    时间: 2011-7-21 02:11

8# zdzjs


批处理函数库里面有计算日期、星期、时间等
http://bbs.bathome.net/thread-3056-1-1.html
作者: 810126769    时间: 2013-8-22 14:17

刚开始学习批处理,太复杂了,还看不太懂啊!
作者: PowerShell    时间: 2013-8-22 18:47

我写了篇 powershell的时间计算的教程,
楼主这个叫[原创] 批处理中的时间计算详解
我那个也是原创,叫做 powershell送你把,解决日期计算类问题的金钥匙! 在 http://www.bathome.net/thread-25932-1-1.html 觉得bat难的,不妨看看powershell的方法.
我那个是调用库,细节并不外露.而楼主这篇写的很细,很透彻.完全是自己的算法实现.
互相印证,相得益彰,此两篇实在是难得的佳作矣




欢迎光临 批处理之家 (http://bathome.net./) Powered by Discuz! 7.2