Board logo

标题: [数值计算] [已解决]批处理日期计算的代码如何理解? [打印本页]

作者: zc584267913    时间: 2010-5-2 07:50     标题: [已解决]批处理日期计算的代码如何理解?

  1. @echo off&setlocal enabledelayedexpansion
  2. set yyyy=%date:~0,4%
  3. set mm=%date:~5,2%
  4. set dd=%date:~8,2%
  5. set /a od=!dd!-1
  6. if !od!==0 call :dd0
  7. if !mm!==0 call :mm0
  8. set yyyymmdd=!yyyy!年!mm!月!od!日
  9. echo 昨天是:!yyyymmdd!
  10. pause
  11. :dd0
  12. set /a mm=!mm!-1
  13. for %%a in (1 3 5 7 8 10 12)do set %%add=31
  14. set /a pddd=!yyyy!*10/4
  15. set pd2d=!pddd:~-1,1!
  16. set 2dd=28
  17. if !pd2d!==0 set 2dd=29
  18. for %%b in (4 6 9 11)do set %%bdd=30
  19. set od=!%mm%dd!
  20. goto :eof
  21. :mm0
  22. set /a yyyy=!yyyy!-1
  23. set mm=12 && set od=31
  24. goto :eof
复制代码
请问set od=!%mm%dd!其中的dd是如何定义的?是否代表的2dd/add/bdd,为何可以这样定义呢?{}

[ 本帖最后由 zc584267913 于 2010-5-12 05:51 编辑 ]
作者: hanyeguxing    时间: 2010-5-2 08:27

1,
原帖由 zc584267913 于 2010-5-2 07:50 发表
请问set od=!%mm%dd!其中的dd是如何定义的?是否代表的2dd/add/bdd,为何可以这样定义呢?

%mm%dd是变量名,!%mm%dd!是其变量的值,分别由以下代码定义:
for %%a in (1 3 5 7 8 10 12)do set %%add=31
set 2dd=28
if !pd2d!==0 set 2dd=29
for %%b in (4 6 9 11)do set %%bdd=30

实际存在的相关变量名为:1dd、2dd、3dd、4dd、5dd、6dd、7dd、8dd、9dd、10dd、11dd、12dd
例如for %%b in (4 6 9 11)do set %%bdd=30实际上分别定义的是4dd、6dd、9dd、11dd
例如当mm为5时,实际set od=!%mm%dd!就是执行set od=!5dd!,把5dd这个变量的值赋给od


2,上面的批处理不够严谨:
例如当月份或日期为08、09时,则产生set/a运算错误。
例如没有考虑闰年的严谨计算。
例如输出月份、日期不规范,要么都加前缀0,要么都不加。
  1. @echo off&setlocal enabledelayedexpansion
  2. for /f "tokens=1-3 delims=-:/ " %%a in ("%date%") do (set Y=%%a&set M=%%b&set D=%%c&if "!M:~0,1!"=="0" set M=!M:~1!
  3. if "!D:~0,1!"=="0" set D=!D:~1!)
  4. set/a D-=1&if !D! leq 0 (set/a M-=1&if !M!==0 set/a Y-=1,M=12
  5. set/a "T=^!(M-2)","R=(^!(Y%%4)&^!^!(Y%%100))|^!(Y%%400)","C=^!(M-4)|^!(M-6)|^!(M-9)|^!(M-11)","D=T*(28+R)+C*30+(^!T&^!C)*31"+D)
  6. set M=0%M%&set D=0%D%&echo.昨天是%Y%年!M:~-2!月!D:~-2!日&pause
复制代码

[ 本帖最后由 hanyeguxing 于 2010-5-10 16:52 编辑 ]
作者: sgaizxt001    时间: 2010-5-2 08:41

师傅你还没睡觉呢。你的代码看起来比别人得要头晕的多。一大行的
作者: hanyeguxing    时间: 2010-5-2 08:52

那这样写好了:
  1. @echo off&setlocal enabledelayedexpansion
  2. for /f "tokens=1-3 delims=-:/ " %%a in ("%date%") do (
  3. set Y=%%a&set M=%%b&set D=%%c
  4. if "!M:~0,1!"=="0" set M=!M:~1!
  5. if "!D:~0,1!"=="0" set D=!D:~1!
  6. )
  7. set/a D-=1
  8. if %D% leq 0 (
  9. set/a M-=1
  10. if !M!==0 set/a Y-=1,M=12
  11. set/a "T=^!(M-2)","R=(^!(Y%%4)&^!^!(Y%%100))|^!(Y%%400)","C=^!(M-4)|^!(M-6)|^!(M-9)|^!(M-11)","D=T*(28+R)+C*30+(^!T&^!C)*31+D"
  12. )
  13. set M=0%M%&set D=0%D%
  14. echo.昨天是%Y%年%M:~-2%月%D:~-2%日
  15. pause
复制代码

[ 本帖最后由 hanyeguxing 于 2010-5-10 16:41 编辑 ]
作者: zc584267913    时间: 2010-5-4 17:53

寒夜大大,貌似你写的代码有点问题哦,我5月4号运行的,计算出来的是4月29日哦````

还有就是下面那段小弟有点看不明白,麻烦解释一下```如set/a "T=^!(M-2)"中的
^!是什么意思?

set/a "T=^!(M-2)","R=(^!(Y%%4)&^!^!(Y%%100))|^!(Y%%400)","C=^!(M-4)|^!(M-6)|^!(M-9)|^!(M-11)","D=T*(28+R)+C*30+(^!T&^!C)*31+D"
作者: hanyeguxing    时间: 2010-5-4 20:51     标题: 回复 5楼 的帖子

4楼的代码里给弄丢了个!,已修改
作者: zc584267913    时间: 2010-5-6 07:42

请问一下,set/a "T=^!(M-2)"中的^!是当作逻辑运算异和非在使用,还是用^来将特殊字符转义呢?
寒夜大大可以麻烦解释一下下面这句代码吗,有点看不懂。。。。。
[set/a "T=^!(M-2)","R=(^!(Y%%4)&^!^!(Y%%100))|^!(Y%%400)","C=^!(M-4)|^!(M-6)|^!(M-9)|^!(M-11)","D=T*(28+R)+C*30+(^!T&^!C)*31+D")]
作者: neorobin    时间: 2010-5-6 09:03     标题: 回复 7楼 的帖子

^! 是用 ^ 转义作用, 将 ! 转为逻辑非运算符号, 在用 setlocal enabledelayedexpansion 打开了延迟的变量扩展时, 成对的 ! 会被处理为变量扩展之义, 所以逻辑非符号 ! 需转义
位异或 ^ 也需转义, 可运行如下代码查看不用转义有何不同
  1. set /a r=3^7,r1=3^^7
  2. set r
  3. pause
复制代码

T=^!(M-2)  是判断是否2月, 是2月,T得1,否则得0; 其它类推.
^!^!(Y%%100) 判断 Y 是否不被100整除, 不被整除得1, 否则得0, 这里虽是两次逻辑非运算, 但并不能抵消为不用逻辑非运算.
CMD 下 逻辑非的规则为: 若0得1, 非0得0
当 Y不被100整除且 对100的余数不为1时, 不用两次取非就得不到1的结果

[ 本帖最后由 neorobin 于 2010-5-6 09:59 编辑 ]
作者: zc584267913    时间: 2010-5-7 16:22

请问下
set /a r=3^7,r1=3^^7
set r
pause
中的^和^^分别的什么意思?为什么结果是37是4

另外^!^!(Y%%100) 为什么要进行两次逻辑非运算?Y%%100中要为什么要使用两个%?

R=(^!(Y%%4)&^!^!(Y%%100))中的为什么要加上一个&符?
作者: neorobin    时间: 2010-5-7 17:03

Q: ^和^^分别的什么意思?为什么结果是37是4

A:
3^7 只是对7进行了转义, 7 还是 7, 故结果成了 37
3^^7 则是对 ^ 进行了转义, 可以理解为单个的 ^ 是一个转义前缀字符, 当它后面是其它字符时, 会发生可能的转义作用, 比如将一个有语法意义的字符转义为一个普通字符.
而两个 ^ 在一起时, 就是对转义前缀字符自身进行了转义, 使它没有了转义前缀的这个作用, 但由于在 set /a 后的表达式中, 所以它仍有另一个语法作用, 即 位异或.
3 的 二进制表达为 11, 而 7 的二进制表达为 111, 位异或的规则为: 同则0, 异则1 -- 两操作数对应位相同, 结果的对应位取0; 两操作数对应位相异, 结果的对应位取1.
  1. 011
  2. 111  (这里只取了最低的三位作 位异或 运算, 事实上, 其它未列出的高位上都是 0, 使得结果的对应高位上也一样取了 0)
  3. --------
  4. 100
复制代码
二进制 100 的表达即 十进制数 4.

Q: ^!^!(Y%%100) 为什么要进行两次逻辑非运算?

A:
此问题在 8 楼已作说明, 可能只是不够详细, 强调指出, 这个部分表达式的结果必须为 0 或者 1, 否则会使后面的计算发生错误.
举例来说: Y为2002时, Y%%100 得2, 第一次取非得 0, 再对 0 取非, 得 1. 这样把不是 1 的结果变成了 1.
Y为2000时,  Y%%100 得0, 第一次取非得 1, 再对 1 取非, 得 0. 即两次取非 与不取非的结果是一样的. 但前提是 Y可以被100整除, 即余数是0.

用 0 来表示一个逻辑假值, 数值上是唯一的.
用不等于 0 的值 来表示一个逻辑真值, 但数值上却不是唯一的了, 因为不等于 0 的数有无数个. 但只用 1 来表示一个逻辑真值, 这样数值上也就是唯一的了.

Q: Y%%100中要为什么要使用两个%?

A:
首先引用 ntcmds.chm 中文版原文
Cmd.exe 提供批处理参数扩展变量(%0 到 %9)。当在批处理文件中使用批处理参数时,%0 将由批处理文件名替换,而 %1 到 %9 将由在命令行键入的相应参数替换。要访问大于 %9 的参数,必须使用 shift 命令。
call [[Drive:][Path] FileName [BatchParameters]] [:label [arguments]]

参数
...
arguments
对于以 :label 打头的批处理程序,指定要传送给其新实例的命令行信息,包括命令行选项、文件名、批处理参数(从 %1 到 %9)或者变量(比如 %baud%)。

如果不使用 双写的 % , 首先求余运算必然与 批处理参数发生混淆, 其次还可能与 变量扩展发生混淆, 所以双写转义就是来避免发生语法歧义混淆的一种手段.



Q: R=(^!(Y%%4)&^!^!(Y%%100))中的为什么要加上一个&符?

A:
此处的 & 是 按位与 运算符, 并不是严格意义的逻辑与运算符, 但一些情况下可与逻辑与等效.

闰年的判断规则为: Y表示年份数字, ((Y能被4整除)且(Y不被100整除))或(Y能被400整除)
这里我用了严格的 逻辑联接词(且 或)  和 括号 来表达这个规则
代码中 求余运算后 都用了 一次或两次逻辑非 运算, 可以保证结果都转化为 0 或者 1, 这样逻辑真值和假值在数值上都是唯一的了, 就可以保证 其后的 按位与 运算 的结果 与 逻辑与运算 的结果是等效的. (数值上不唯一的两个真值会导致按位与运算的结果为假值 0, 例如 set /a "2&4" 的结果便得 0)

(^!(Y%%4)&^!^!(Y%%100)) 的意义即闰年规则中的部分语句:   ((Y能被4整除)且(Y不被100整除)),  其中 逻辑非 ^! 的优先级高于 按位与

[ 本帖最后由 neorobin 于 2010-5-7 18:08 编辑 ]
作者: hanyeguxing    时间: 2010-5-8 09:23     标题: 回复 9楼 的帖子

注意两个set/a之间的区别:
  1. @echo off
  2. for %%a in (1900 2000 2008 2010 2012 2100 2400) do (
  3. set/a "A=(!(%%a%%4)&!!(%%a%%100))|!(%%a%%400)"
  4. setlocal enabledelayedexpansion
  5. set/a "B=(^!(%%a%%4)&^!^!(%%a%%100))|^!(%%a%%400)"
  6. echo.%%a:!A!和!B!
  7. Endlocal
  8. )
  9. pause
复制代码

[ 本帖最后由 hanyeguxing 于 2010-5-8 09:25 编辑 ]
作者: zc584267913    时间: 2010-5-10 11:30     标题: 回复 10楼 的帖子

请问下
^所转义的范围是什么?是只能对数字及字符进行转义吗?比如r=3^7=37,r=-3^-1=-31,但为什么r=3^-1=2呢?是否在set/a后逻辑运算符和位运算符都无法转义吗?

^!(Y%%4)&^!^!(Y%%100))|^!(Y%%400)"
(Y能被4整除)且(Y不被100整除))或(Y能被400整除)
格式都是一样的,为什么中间的Y%%100是Y不被100整除呢?
作者: neorobin    时间: 2010-5-10 12:23     标题: 回复 12楼 的帖子

原帖由 zc584267913 于 2010-5-10 11:30 发表
请问下
^所转义的范围是什么?是只能对数字及字符进行转义吗?比如r=3^7=37,r=-3^-1=-31,但为什么r=3^-1=2呢?是否在set/a后逻辑运算符和位运算符都无法转义吗?

^!(Y%%4)&^!^!(Y%%100))|^!(Y%%400)"
(Y能被4 ...


set /a r=-3^-1 的结果是 -4 而不是 -31, - 被转义后意义仍为原来的语法意义 负号 或 减法

引用 ntcmds.chm 中文版
地址: ms-its:C:\WINDOWS\Help\ntcmds.chm::/ntcmds_shelloverview.htm
可以将大多数字符用作变量值,其中包括空格。如果使用特殊字符 <、>、|、& 或 ^,则必须在它们前面加上转义字符 (^) 或引号。如果使用引号,则必须将引号作为值的组成部分,因为等号后面的任何内容都会被视为值。


Q: ^所转义的范围是什么?
对于转义范围的问题, 我也未有更深入的认识.
请在论坛搜索相关内容及观察 运行结果 随 代码中运用转义情形 而发生的变化

^!(Y%%4) 是指 Y 是否能被 4 整除, 注意含有一次逻辑非运算

中间的 Y%%100, 加上 两次 逻辑非运算 后就(也才)是指 Y 是否不能被 100 整除, 如果只加上 一次 逻辑非运算, 便是 Y 是否能被 100 整除
作者: hanyeguxing    时间: 2010-5-10 13:54

原帖由 zc584267913 于 2010-5-10 11:30 发表
请问下
^所转义的范围是什么?是只能对数字及字符进行转义吗?比如r=3^7=37,r=-3^-1=-31,但为什么r=3^-1=2呢?是否在set/a后逻辑运算符和位运算符都无法转义吗?

^!(Y%%4)&^!^!(Y%%100))|^!(Y%%400)"
(Y能被4 ...

set/a r=3^7,在预处理时,7本身就是普通字符,^直接被脱去,实际运行的是set/a r=37
set/a r=-3^-1,在预处理时,-本身就是普通字符,^直接被脱去,实际运行的是set/a r=-3-1,结果为-4
set/a r=3^-1,在预处理时,-本身就是普通字符,^直接被脱去,实际运行的是set/a r=3-1,结果为2
以上的7和-没有必要被转义,因为他们在预处理时本就不起作用,也就没必要让他们不起作用。
看这个示例,例如:
  1. set/a set=1&set
  2. set/a set=1^&set
  3. @echo.%set%&pause
复制代码
set/a set=1&set,预处理时,因为&没被转义,所以起连接作用。实际运行时,这一条语句被分成两个并列的部分,即set/a set=1和set,set/a set=1完成运算赋予值,set变量为1,set命令显示环境变量。在这里第一和第三个set是命令,第二个是变量名。

set/a set=1^&set,预处理时,&因为被转义,所以不起连接作用,^被脱去,实际运行的是set/a set=1&set,这时&就起了运算符的作用。在这里第一个set是命令,第二和第三个是变量名。

至于^是不是转义符,这要根据实际命令决定,如下示例:
  1. @echo off
  2. for /f %%i in ('dir /b/ad ^|findstr ^[0-9]*[0-9]$') do echo.%%i
  3. pause
复制代码
这个批处理是用来查找当前目录下以数字命名的目录。第一个^是转义符,他使预处理时“|”被作为普通字符处理,而不是管道。第二个^是findstr的一部分,用来定义行首的,不是转义符。

看一个复杂的:set/a "R=(^!(Y%%4)&^!^!(Y%%100))|^!(Y%%400)"
这里实际引号“""”也起着类似转义的作用,即预处理阶段&和|不作为连接和管道的作用,但引号不能处理 ! 在运行时作为变量延迟标志的作用,所以就用^来转义。上面的代码是工作在开启变量延迟的状态下。如果不开启,则可以写成:set/a "R=(!(Y%%4)&!!(Y%%100))|!(Y%%400)"
如果不想使用引号,则需要写成:set/a R=(!(Y%%4)^&!!(Y%%100))^|!(Y%%400)

所以想弄明白^的转义作用,就要弄明白他和引号、预处理机制之间的关系。

[ 本帖最后由 hanyeguxing 于 2010-5-10 18:30 编辑 ]
作者: zc584267913    时间: 2010-5-10 15:58

可能是小弟太愚钝了,最终还是没有计算出来下面这句的值,求详细计算过程,已2010-05-10号为例
set/a "T=^!(M-2)","R=(^!(Y%%4)&^!^!(Y%%100))|^!(Y%%400)","C=^!(M-4)|^!(M-6)|^!(M-9)|^!(M-11)","D=T*(28+R)+C*30+(^!T&^!C)*31+D")
作者: hanyeguxing    时间: 2010-5-10 17:07

原帖由 zc584267913 于 2010-5-10 15:58 发表
可能是小弟太愚钝了,最终还是没有计算出来下面这句的值,求详细计算过程,已2010-05-10号为例
set/a "T=^!(M-2)","R=(^!(Y%%4)&^!^!(Y%%100))|^!(Y%%400)","C=^!(M-4)|^!(M-6)|^!(M-9)|^!(M-11)","D=T*(28+R)+C*30+(^!T&^!C)*31+D")

你没弄懂的是其他部分的代码:
@echo off&setlocal enabledelayedexpansion关闭回显,不显示本行已经开启变量延迟
for /f "tokens=1-3 delims=-:/ " %%a in ("%date%") do (
set Y=%%a&set M=%%b&set D=%%c
if "!M:~0,1!"=="0" set M=!M:~1!
if "!D:~0,1!"=="0" set D=!D:~1!
)
上面是获取年、月和日,分别赋值给Y、M和N。
set/a D-=1
将日期减1就是昨天的日期,但有一个例外,那就是如果今天是1号,那么D为0。实际昨天应该是上个月的月末,实际数字就是上个月的天数。所以下面的代码是用来处理这个特例的,也就是计算上个月的天数。
if %D% leq 0
处理D为0的情况,也可以直接写成if %D%==0,这里使用 leq 是为了方便计算前、大前天时,就只需要改set/a D-=1就可以了。
set/a M-=1
当D为0时,需要获取上个月的月份
if !M!==0 set/a Y-=1,M=12
如果M为0则,年为去年,月份为12
set/a "T=^!(M-2)","R=(^!(Y%%4)&^!^!(Y%%100))|^!(Y%%400)","C=^!(M-4)|^!(M-6)|^!(M-9)|^!(M-11)","D=T*(28+R)+C*30+(^!T&^!C)*31+D"
获取上个月的某个日期,D为0时即获取月份的天数(月末的日期)。
set M=0%M%&set D=0%D%
为月和日前面加0
echo.昨天是%Y%年%M:~-2%月%D:~-2%日
显示年月日,并截取月和日的后两位数字。
注意,不要认为变量名相同,就认为他代表的意思相同!

set/a "T=^!(M-2)","R=(^!(Y%%4)&^!^!(Y%%100))|^!(Y%%400)","C=^!(M-4)|^!(M-6)|^!(M-9)|^!(M-11)","D=T*(28+R)+C*30+(^!T&^!C)*31+D"
重点说下这个:
这个是在D为0时的代码,所以"D=T*(28+R)+C*30+(^!T&^!C)*31+D"可以直接写成"D=T*(28+R)+C*30+(^!T&^!C)*31"。这里之所以加上这个D,是为了方便代码改成计算前天、大前天的日期。
"T=^!(M-2)"如果月份为2,则T为1,其他时候为0
"R=(^!(Y%%4)&^!^!(Y%%100))|^!(Y%%400)"闰年时为1,非闰年是为0
"C=^!(M-4)|^!(M-6)|^!(M-9)|^!(M-11)"如果月份为小月(30天的月份),则C为1,其他时候,包括大月(31天)和2月时,为0
"D=T*(28+R)+C*30+(^!T&^!C)*31"获取月份的天数,其中(^!T&^!C)如果月份为非小月,也非2月,实际就是大月的时候,此结果为1。
现在以2010年5月为例:
set/a "T=^!(M-2)",M减2为3,!的作用是对3取非,即0时为1,非0时为0,所以T为0,T只有在M为2时才为1。
set/a "R=(^!(Y%%4)&^!^!(Y%%100))|^!(Y%%400)",Y为2010,R为0。
set/a "C=^!(M-4)|^!(M-6)|^!(M-9)|^!(M-11)",只有当M为4或6或9或11时,C为1,其他时候如M为1、2、3、5、7、8、10和12时,C为0,所以此时C为0
set/a "D=T*(28+R)+C*30+(^!T&^!C)*31+D",因为T为0,C为0,所以(^!T&^!C)为1,所以这个算式就是set/a "D=0*(28+0)+0*30+1*31+0",结果31。

如果要获取某个月,如当月的天数,可以这样写:
  1. @echo off&setlocal enabledelayedexpansion
  2. for /f "tokens=1,2 delims=-:/ " %%a in ("%date%") do (
  3. set M=%%b&if "!M:~0,1!"=="0" set M=!M:~1!
  4. set/a "C=^!(M-4)|^!(M-6)|^!(M-9)|^!(M-11)","E=^!(M-2)*(28+(^!(%%a%%4)&^!^!(%%a%%100))|^!(%%a%%400))+C*30+(^!^!(M-2)&^!C)*31"
  5. )
  6. echo.%E%&pause
复制代码

[ 本帖最后由 hanyeguxing 于 2010-5-10 17:13 编辑 ]




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