[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程[批处理精品]批处理版照片整理器
[批处理精品]纯批处理备份&还原驱动[批处理精品]CMD命令50条不能说的秘密[在线下载]第三方命令行工具[在线帮助]VBScript / JScript 在线参考
返回列表 发帖

[日期时间] [已更新]批处理单行版时间日期计算 算法讨论

本帖最后由 plp626 于 2012-4-15 11:55 编辑

原来讨论的问题描述较长,为方便大部分人分享讨论结果,把代码放在一楼,原讨论的问题移至2楼;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

下面仅是零星的一些函数;功能比较单一,是为了大家可以根据需求自己定制功能;
核心的两个函数date2i和i2date算法思想参加15楼分析;

生活实用版:1900年3月1日~2100年3月1日(不含)之间的日期推算
  1. :: date2i // 求1900-3-1到y-m-d所经历的天数i; y-m-d 范围:[1900-3-1, 2100-3-1)
  2. set/a "m=(m+9)%%12,y-=m/10+1900,i=365*y+y/4+(m*153+2)/5+d-1"
复制代码
  1. :: i2date // 求1900-3-1日后第i天的日期; i 的范围:[0, 73049)
  2. set/a "y=(4*i+3)/1461,t=i-y*365-y/4,m=(t*5+2)/153+2,d=t-(m*153-304)/5+1,y+=m/12+1900,m=m%%12+1"
复制代码
数值计算版(欢迎大家测试):0-3-1 (序号0)~ 33301-3-1(序号12162940,不含该序号) 之间的日期推算
  1. :date2i <year> <month> <day> <RetVarName>
  2. setlocal&set/a y=%1,m=%2,d=%3
  3. set/a m=(m+9)%%12,y-=m/10,i=365*y+y/4-y/100+y/400+(m*153+2)/5+d-1
  4. endlocal&set %4=%i%&goto:eof
复制代码
  1. :i2date <index> <Ret1> <Ret2> <Ret3>
  2. setlocal&set/a i=%1, y=(4*i+999)/1461
  3. set/a "y+=i-y*365-y/4+y/100-y/400>>9,t=i-y*365-y/4+y/100-y/400"
  4. set/a "m=(t*5+2)/153,d=t-(m*153+2)/5+1,y+=(m+2)/12,m=(m+2)%%12+1"
  5. endlocal&set/a %2=%y%,%3=%m%,%4=%d%&goto:eof
复制代码
-----------------------------定制---------------------------------------
  1. ::  获取指定日期的星期数;返回值为0表示星期日,返回值为1表示星期1,。。。类推
  2. :date2week <year> <month> <day> <RetVarName>
  3. call:i2date %*&set/a %4=(%4+3)%%7&goto:eof
复制代码
  1. @echo off
  2. :: # 获取指定日期之前或之后的日期 nextdate
  3. :: 日期格式(仅支持月数,日数前面 有1位0的格式):
  4. ::           2012-1-01,2012-01-11,2012-1-1,1941-10-01,...
  5. :: 适用范围: 不好描述,完全够日常生活使用;
  6. :: 要计算100年后或100年前的,请选择数值计算版date2i;i2date进行定制;
  7. rem  函数调用格式演示:
  8. call:nextdate mydate="2012-1-1" -7
  9. echo %mydate%
  10. call:nextdate mydate="2012-01-1" -7
  11. echo %mydate%
  12. call:nextdate mydate="2012-1-01" -7
  13. echo %mydate%
  14. call:nextdate mydate="2012-01-01" -7
  15. echo %mydate%
  16. pause&goto:eof
  17. :nextdate <RetVarName> <"DATE"> <[+|-]int> // 获得 给定日期第n天 的日期
  18. setlocal&set tp=%~2
  19. for /f "tokens=1-3delims=-" %%a in ("%tp:-0=-%")do (
  20.   set/a "y=%%a,m=%%b,d=%%c+%3,m=(m+9)%%12,y-=m/10+1900"
  21.   set/a "i=365*y+y/4+(m*153+2)/5+d-1,y=(4*i+3)/1461,t=i-y*365-y/4"
  22.   set/a "m=(t*5+2)/153+2,d=t-(m*153-304)/5+1,y+=m/12+1900,m=m%%12+1"
  23. )
  24. endlocal&set %1=%y%-%m%-%d%&goto:eof
复制代码
  1. :: 求两个日期相隔的天数
  2. :edate <RetVarName> <"DATE1"> <"DATE2">  
  3. setlocal&set d1=%~2&set d2=%~3
  4. :: 参考代码
  5. set d1=%d1:-= %&set d2=%d2:-= %
  6. call :date2i %d1: 0= % r1
  7. call :date2i %d2: 0= % r2
  8. set/a c=r2-r1
  9. endlocal&set ans.%~1=%c%&set ans.%~1&goto:eof
复制代码
2

评分人数

    • CrLf: 有意义,有技术PB + 15 技术 + 2
    • Batcher: Good work!技术 + 1

本帖最后由 plp626 于 2012-4-15 09:37 编辑

{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
本贴主要讨论时间日期计算的算法思想(几乎纯数学,外加set/a运算技巧)
可参照 HAT翻译了Ritchie Lawrence 的时间日期函数库;为引子,开启大家思路;

方便交流,做如下约定:
一个时间变量(可以称作复合型变量)由6个整数组成,分别为:【年数,月数,日数,时数,分数,秒数】
  1. :: 方便讨论这6个整数分别用如下字母表示[y,m,d,h,f,s]
  2. y取值范围[-10000,10000];
  3. (暂时不考虑y为负值的情形,只考虑公元1年1月1日之后的情形;视难度而定)
  4. m取值范围[1,12]
  5. d取值范围[1,31]
  6. h取值范围[0,23]
  7. f取值范围[0,59]
  8. s取值范围[0,59]
  9. :: 比如 2012-04-03,1:02:09秒这个时间对应的值为 y=2012,m=4,d=3,h=1,f=2,s=9
复制代码
本贴重点是算法思想,阐述思路是重点;
代码虽然作为载体不很重要,但也请给出最简短高效的实现代码;
(在便于阅读的条件下,一行set/a 实现最好)

主要解决一下问题;
(一)给定两个时间,求其相隔的天数;或者秒数
Q1:
     y1,m1,d1,h1,f1,s1 (初时间); y1,m1,d1,h2,f2,s2(末时间) // 初末时间符合客观现实
     求其相隔多少秒sec
  1. :fun1
  2. set/a y=2012,m=4,d=3,h1=1,f1=2,s1=9
  3. set/a                h2=1,f2=3,s2=9
  4. rem ---- 你的代码1 -----
  5. echo %out%
  6. rem 输出值out=60
复制代码
Q2:  
     y1,m1,d1,h1,f1,s1 (初时间); y2,m2,d2,h1,f1,s1(末时间) // 初末时间符合客观现实
     求其相隔多少天day
  1. :fun2
  2. set/a y1=2012,m1=4,d1=3,h=1,f=2,s=9
  3. set/a y2=2012,m2=5,d2=3
  4. rem ---- 你的代码1 -----
  5. echo %out%
  6. rem 输出值out=31
复制代码
Q3:
     y1,m1,d1,h1,f1,s1 (初时间); y2,m2,d2,h2,f2,s2(末时间) // 初末时间符合客观现实
     求其相隔多少天day+多少秒sec (day,sec 为 非负数,day范围[0,+2147483647]sec范围为[0,86399])
  1. :fun3
  2. set/a y1=2012,m1=4,d1=3,h1=1,f1=2,s1=9
  3. set/a y2=2012,m2=4,d2=5,h2=1,f2=2,s2=10
  4. rem ---- 你的代码1 -----
  5. echo %out%
  6. rem 输出值out=2 1
复制代码
(二)给定一个时间,y,m,d,h,f,s 求其 x天或x秒之后的时间
Q4:
       给定 y,m,d,h,f,s;求x天后的;y,m,d,h,f,s (x为负数表示之前,x范围:[-2147483647, +2147483648])
  1. :fun4
  2. set/a y=2012,m=4,d=3,h=1,f=2,s=9
  3. set/a x=-4
  4. rem ---- 你的代码 -----
  5. echo %out%
  6. rem 输出值out=2012 3 30 1 2 9
复制代码
Q5:
   给定 y,m,d,h,f,s;求x秒后的;y,m,d,h,f,s (x为负数表示之前,x范围:[-86399, +86399])
  1. :fun5
  2. set/a y=2012,m=4,d=3,h=1,f=2,s=9
  3. set/a x=-61
  4. rem ---- 你的代码 -----
  5. echo %out%
  6. rem 输出值out=2012 4 3 1 1 8
复制代码
(三)给定一个日期,求其星期数
  1. :fun6 给定一个日期,求其星期数
  2. set/a y=2012,m=4,d=3
  3. rem ---- 你的代码 -----
  4. echo %out%
  5. rem 输出值out=2
复制代码
为建立适合cmd的时间变量数据结构,增加一个成员;跑秒,即百分之一秒;

这样一个时间变量有7个成员(年,月,日,时,分,秒,跑秒),为方便交流约定分别用字母(y,m,d,h,f,s,p)表示;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

回复 7# CrLf
Q1就是Q3的特殊情形(同一天内的两时间差)
Q2也是Q3的特殊情形(同一时分秒下,两日期相隔的天数)
对于你说的问题我不够秒数不细致造成误会;sec取值范围为[0,86399](已更新);
把第一类问题分解成Q1,Q2,是有原因的;
一天=86400秒,秒数可以转换为天数,天数也能转换为秒数,那么求时间差,给出day+sec不是多次一举?
非也,set/a 能支持的最大数值2147483647,2147483647sec=24855day=58year
为了能兼容表示超过58年的情形,用两个数表示时间差,day和sec;day相当于时间差值的高位,sec相当于时间差值的低位;
所以,sec的取值范围为[0,86399]
第一类问题的核心是Q2,这里推荐大家参看15楼的思想;
对于第二类问我还没好思路,大家继续参看15楼的思想,交流下;

把算法思想吃透了,代码真就是工具了;
-----------------------------------------------------------

回复 5# CrLf

只测试了你的天数转日期,是把1969-9-11作为参考日期的第0天;只见代码不见思想;
  1. :d2ymd <day> <ret:y> <ret:m> <ret:d>
  2. @echo off
  3. :d2ymd
  4. setlocal enabledelayedexpansion
  5. set/a d=%1
  6. set/a "d+=719050,d-=d/1461-d/36524+d/146097,y=d/365+1,r=^!(y%%4)-^!(y%%100)+^!(y%%400),d+=(d%%=365)/(212+r)*30+r,d+=^!^!(d/(59+r))*(2-r),m=d/61*2+d%%61/31,d-=m*61/2+m*61%%2-1,m+=1-m/8"
  7. endlocal&if %4. neq . (set %2=%y%&set %3=%m%&set %4=%d%)else echo %y% %m% %d%
复制代码
保存为d2ymd.bat,发现不少bug:
  1. ->d2ymd 841
  2. 1971 12 31
  3. ->d2ymd 842
  4. 1972 1 2
  5. ->d2ymd 1223
  6. 1973 1 16
  7. ->d2ymd 1222
  8. 1973 1 16
  9. 还有很多bug。。。
复制代码
---------------------------------------------

TOP

我先来:
Question1:给定两个时间(同一天),求相隔的秒数
这个应该是最简单的;
思路描述:
先看看怎么计算327-179
(3-1)*100+(2-7)*10+(7-9)*1
1小时=3600秒
1分钟=60秒
类比得到h1,f1,s1 到 h2,f2,s2
相隔的秒数=(h2-h1)*3600+(f2-f1)*60+s2-s1
  1. set/a y=2012,m=4,d=3,h1=1,f1=2,s1=9
  2. set/a                h2=1,f2=3,s2=9
  3. rem -------------------
  4. set out=(h2-h1)*3600+(f2-f1)*60+s2-s1
  5. rem -------------------
  6. echo %out%
  7. rem 输出值out=60
复制代码

TOP

google:
Date calculation algorithm
有些有价值的参考
1

评分人数

TOP

本帖最后由 CrLf 于 2012-4-4 01:17 编辑

原型:
  1. set/a days=d-719446+30*m+m/9*-~m/2+!(m/9)*m/2+!!(m/3)*(!(y%%4)-!(y%%100)+!(y%%400)-2)+y*365+~-y/4-~-y/100+~-y/400
  2. rem 日期转天数
  3. set/a d+=719050,d-=d/1461-d/36524+d/146097,y=d/365+1,r=!(y%%4)-!(y%%100)+!(y%%400),d+=(d%%=365)/(212+r)*30+r,d+=!!(d/(59+r))*(2-r),m=d/61*2+d%%61/31,d-=m*61/2+m*61%%2-1,m+=1-m/8
  4. rem 天数转日期
  5. set /a m+=!(m/=3)*12,week=(d+2*m+3*(m+1)/5+(y-=m/13)+y/4-y/100+y/400+1)%%7
  6. rem 日期转星期
复制代码

TOP

这个很感兴趣,占位子
#&cls&@powershell "Invoke-Expression ([Io.File]::ReadAllText('%~0',[Text.Encoding]::UTF8))" &pause&exit

TOP

本帖最后由 CrLf 于 2012-4-4 00:41 编辑

Q1:
  1. @echo off
  2. set/a y1=2012,m1=4,d1=3,h1=1,f1=2,s1=9
  3. set/a y2=y1,m2=m1,d2=d1,h2=1,f2=3,s2=9
  4. set/a out=(d2-d1+30*(m2-m1)+m2/9*-~m2/2+!(m2/9)*m2/2+!!(m2/3)*(!(y2%%4)-!(y2%%100)+!(y2%%400)-2)-m1/9*+~m1/2-!(m1/9)*m1/2-!!(m1/3)*(!(y1%%4)-!(y1%%100)+!(y1%%400)-2)+(y2-y1)*365+~-y2/4-~-y2/100+~-y2/400-~-y1/4+~-y1/100-~-y1/400)*86400+(h2-h1)*3600+(f2-f1)*60+s2-s1
  5. echo %out%
  6. rem 输出值out=60
  7. pause
复制代码
思路:
  1. 年份之差*365+天数之差
  2. 按大小月、平闰年修正天数
  3. 所得天数乘一日总秒数
  4. 最后加上秒数差即可
复制代码
其余几个命题同样可依照几个原型公式稍加修改


与 Q1 同类的:
Q2:
  1. @echo off
  2. set/a y1=2012,m1=4,d1=3,h1=1,f1=2,s1=9
  3. set/a y2=y1,m2=5,d2=3,h2=1,f2=2,s2=9
  4. set/a out=d2-d1+30*(m2-m1)+m2/9*-~m2/2+!(m2/9)*m2/2+!!(m2/3)*(!(y2%%4)-!(y2%%100)+!(y2%%400)-2)-m1/9*+~m1/2-!(m1/9)*m1/2-!!(m1/3)*(!(y1%%4)-!(y1%%100)+!(y1%%400)-2)+(y2-y1)*365+~-y2/4-~-y2/100+~-y2/400-~-y1/4+~-y1/100-~-y1/400+((h2-h1)*3600+(f2-f1)*60+s2-s1)/86400
  5. echo %out%
  6. rem 输出值out=31
  7. pause
复制代码
Q3:
  1. @echo off
  2. set/a y1=2012,m1=4,d1=3,h1=1,f1=2,s1=9
  3. set/a y2=2012,m2=4,d2=5,h2=1,f2=2,s2=10
  4. set/a days=d2-d1+30*(m2-m1)+m2/9*-~m2/2+!(m2/9)*m2/2+!!(m2/3)*(!(y2%%4)-!(y2%%100)+!(y2%%400)-2)-m1/9*+~m1/2-!(m1/9)*m1/2-!!(m1/3)*(!(y1%%4)-!(y1%%100)+!(y1%%400)-2)+(y2-y1)*365+~-y2/4-~-y2/100+~-y2/400-~-y1/4+~-y1/100-~-y1/400,secs=(h2-h1)*3600+(f2-f1)*60+s2-s1
  5. set out=%days% %secs%
  6. echo %out%
  7. rem 输出值out=2 1
  8. pause
复制代码
顺便说一下,楼主似乎没有明确说明天数和秒数的概念,天数的计算是否要精确到秒?秒数的取值范围有多大(需要将相隔天数转为对应秒数吗)?

TOP

Q4:
  1. @echo off
  2. set/a y=2012,m=4,d=3,h=1,f=2,s=9
  3. set/a x=-4
  4. set/a d=d-397+30*m+m/9*-~m/2+!(m/9)*m/2+!!(m/3)*(!(y%%4)-!(y%%100)+!(y%%400)-2)+y*365+~-y/4-~-y/100+~-y/400+x,d-=d/1461-d/36524+d/146097,y=d/365+1,r=!(y%%4)-!(y%%100)+!(y%%400),d+=(d%%=365)/(212+r)*30+r,d+=!!(d/(59+r))*(2-r),m=d/61*2+d%%61/31,d-=m*61/2+m*61%%2-1,m+=1-m/8
  5. set out=%y% %m% %d% %h% %f% %s%
  6. echo %out%
  7. rem 输出值out=2012 4 3 1 1 8
  8. pause
复制代码
思路:
  1. 取天数加上偏移量
  2. 计算闰年数量取得年数
  3. 取得月份数量
  4. 取得天数(好像都是废话...)
复制代码
大同小异的 Q5:
  1. set/a s=h*3600+f*60+s+x,d=d-397+30*m+m/9*-~m/2+!(m/9)*m/2+!!(m/3)*(!(y%%4)-!(y%%100)+!(y%%400)-2)+y*365+~-y/4-~-y/100+~-y/400+s/86400,d-=d/1461-d/36524+d/146097,y=d/365+1,r=!(y%%4)-!(y%%100)+!(y%%400),d+=(d%%=365)/(212+r)*30+r,d+=!!(d/(59+r))*(2-r),m=d/61*2+d%%61/31,d-=m*61/2+m*61%%2-1,m+=1-m/8,h=s/3600%%60,f=s/60%%60,s%%=60
复制代码

TOP

Q6 照搬函数里的现成公式...
  1. @echo off
  2. set/a y=2012,m=4,d=3
  3. set /a m+=!(m/=3)*12,out=(d+2*m+3*(m+1)/5+(y-=m/13)+y/4-y/100+y/400+1)%%7
  4. echo %out%
  5. rem 输出值out=2
  6. pause
复制代码

TOP

各种大神
各种葱白
早中晚各问自己一遍:你平均每周帮助别人解决几个问题?

TOP

本帖最后由 terse 于 2012-4-4 03:35 编辑

个人较倾向于转换成儒略日 然后计算
  1. @echo off&setlocal enabledelayedexpansion
  2. for /l %%i in (1 1 31) do set "D%%i=0%%i"
  3. set "Xq=一二三四五六日"
  4. set "Ymda=1990 1 1"&set "Ymdb=2012 4 3"
  5. for /f "tokens=1-6" %%i in ("%Ymda% %Ymdb%") do set /a "JDA=%%k-32075+1461*(%%i+4800+(%%j-14)/12)/4+367*(%%j-2-(%%j-14)/12*12)/12-3*((%%i+4900+(%%j-14)/12)/100)/4,JDB=%%n-32075+1461*(%%l+4800+(%%m-14)/12)/4+367*(%%m-2-(%%m-14)/12*12)/12-3*((%%l+4900+(%%m-14)/12)/100)/4,JD=JDB-JDA"
  6.    echo %Ymda% 至 %Ymdb% 相隔  %JD% 天
  7.    REM 查询N天前或后的日期;
  8.    set/p N=请输入天数(往前查询加上-如:-10000 表示10000天前的日期。):
  9.    set /a JD=N+JDA,W=JD%%7,JD+=68569,N=(4*JD)/146097,JD-=(146097*N+3)/4,I=(4000*JD+1)/1461001,JD-=(1461*I)/4-31,J=(80*JD)/2447,D=JD-(2447*J)/80,JD=J/11,M=J+2-(12*JD),Y=100*(N-49)+I+JD
  10.    for /f "tokens=1-3" %%i in ("!M! !D! !W!") do echo;你查询的是:!Y!年!D%%i:~-2!月!D%%j:~-2!日 星期!Xq:~%%k,1!
  11. pause
复制代码
1

评分人数

    • CrLf: 这个好PB + 5 技术 + 1

TOP

本帖最后由 CrLf 于 2012-4-4 12:01 编辑

根据儒略日公式化简所得的 Q1 方案:
  1. @echo off&setlocal enabledelayedexpansion
  2. set/a y1=2012,m1=4,d1=3,h1=1,f1=2,s1=9
  3. set/a y2=y1,m2=m1,d2=d1,h2=1,f2=3,s2=9
  4. set /a "out=(1461*(y2+(m2-14)/12)/4+367*(m2-2-(m2-14)/12*12)/12-3*((y2+(m2-14)/12)/100+1)/4-1461*(y1+(m1-14)/12)/4-367*(m1-2-(m1-14)/12*12)/12+3*((y1+(m1-14)/12)/100+1)/4-d1+d2)*86400+(h2-h1)*3600+(f2-f1)*60+s2-s1"
  5. echo %out%
  6. rem 输出值out=60
  7. pause
复制代码
确实比早先那个从元年开始硬算的方案简洁

TOP

关于测试:

对于所有 日期序号 类型的算法: (任何日期对应一个整数, 即序号; 任何一个"明天"的序号=任何一个"今天"的序号+1)
所有正确的算法对两个给定的日期的 差都会是一致的,  
所有正确的算法 得出任何给定的日期的 星期都会是一致的
不同的算法可以取不同的基准日期(可计算范围也可能不同),  用相同的日期参数得到的日期序号可以不同

用 两个日期的差 和 任何日期的星期 在算法间作对比, 可以找出错误的算法, 但不能证明某算法是正确的.

把一个思路非常简单, 非常容易理解的算法 假设 为一个正确的算法.
1

评分人数

    • plp626: 说得不错。。PB + 10

TOP

希望有更多的函数,可是这些函数怎么调用啊?

TOP

日期序号算法分析

本帖最后由 neorobin 于 2012-4-9 15:47 编辑
  1. set /a "m+=9, m%%=12, y-=m/10, index=365*y + y/4 - y/100 + y/400 + (m*306 + 5)/10 + d - 1
复制代码
计算年: 从历法年 x.3.1 日到 (x+1).2.(28/29) 日, 定义为一个计算年,
一个计算年的长度不固定:
365: 当 x+1 是平年份
366: 当 x+1 是闰年份

一个计算年中, 3.1 日称为年始日, 2.(28/29) 日称为年终日
以 0 年 3 月 1 日为基准日期, 序号设为 0, 其后所有日期的序号依次加 1.
基准日期就是第 1 个计算年的年始日.
前 400 个计算年的始日, 终日, 长度, 始日序号, 终日序号如下:
pfirstlastleni(first)i(last)
10.3.11.2.283650364
21.3.12.2.28365365729
32.3.13.2.283657301094
43.3.14.2.2936610951460
...
87.3.18.2.2936625562921
...
9998.3.199.2.283653579436158
10099.3.1100.2.283653615936523
...
400399.3.1400.2.29366145731146096


从基准日期开始, p 个计算年的总天数公式为(全作平年算, 加上 4 的倍数的闰年的个数, 减去整百年份的个数, 再加上 400 的倍数闰年份的个数):

365*p + p/4 - p/100 + p/400

其中包含的最后的年终日和年始日的序号为:

最后一个年终日的序号 = 365*p + p/4 - p/100 + p/400 - 1

最后一个年始日的序号 = 365*q + q/4 - q/100 + q/400      (其中, q=p-1)

对于历法日期 y.m.d, 首先假定它不是一个年终日,
从基准日期到 小于这个日期的最后一个年终日, 就得到 t 个计算年:

t = y:  当 m >= 3 时; 或者 t = y-1:  当 m < 3 时.

计算 t 的代码:
  1. set /a n=m, n+=9, n%=12, t=y, t-=n/10
复制代码
第 t 个年终日的序号 = 365*t + t/4 - t/100 + t/400 - 1
第 t+1 个年始日的序号 = 365*t + t/4 - t/100 + t/400

上述计算中的
  1. set /a n=m, n+=9, n%=12
复制代码
将 m 线性映射到一个新的变量 n, 映射关系为:
m -> n : {3, 4, 5, ... 12, 1, 2} -> {0, 1, 2, ... 9, 10, 11}

n 不再是历法月份的意义, 而是一个计算年中每一个月份对起始月份 3 月的偏移数.
这个 n 将有助于我们构造一个结构简单的离散函数来计算每个月的起始日对 3.1 日的偏移量.

于是:

y.m.d 的序号 = 第 t+1 个年始日(3.1 日)的序号 + n.1 日对 3.1 日的偏移 + d 日对 1 日的偏移

m.1 日对 3.1 日的偏移 用一个关于 n 的近似线性的离散函数来实现:

int(f(n)) = int((n*306 + 5)/10)

这里 int() 是截尾式取整, 即把参数的小数部分直接去掉, 而不是四舍五入的方式.
Monthnf(n)*10int(f(n))int(f(n+1))-int(f(n))
Mar05031
Apr13113130
May26176131
Jun39239230
Jul.4122912231
Aug5153515331
Sep6184118430
Oct7214721431
Nov8245324530
Dec9275927531
Jan10306530631
Feb113371337

第 n 个计算月的天数: Δint(f(n)) = int(f(n+1))-int(f(n))   (n < 11)
Δint(f(n)) 的平均值 = 30.6 ≈ (31+30+31+30+31+31+30+31+30+31+31)/11

上面表中 f(n)*10, int(f(n)), int(f(n+1))-int(f(n)) 这 3 列可用如下代码在命令行运行得到
  1. for /l %a in (0 1 11) do >nul set /a "fn=%a*306+5" & set fn
  2. for /l %a in (0 1 11) do >nul set /a "fn=%a*306+5, ifn=fn/10" & set ifn
  3. for /l %a in (0 1 10) do >nul set /a "delta=((%a+1)*306+5)/10-(%a*306+5)/10" & set delta
复制代码
以上, 对 年始日序号 和 月始日序号偏移 的计算, 为了方便而引入了变量 t 和 n, 现在我们去掉这两个变量,
给出计算日期 y.m.d 的序号 i 的伪代码:

i(y, m, d) {
  set /a "m+=9, m%=12, y-=int(m/10)"
  return 365*y + y/4 - y/100 + y/400 + int((m*306 + 5)/10) + d - 1
}

仅就此函数而言, 返回值也可不要最后的 "- 1", 只是把任意日期的序号增长了 1, 或者认为将日期序号轴的原点左移了一天.

cmd 的代码在本文开头已给出.

下接 25# 序号求日期的算法分析

参考原文:
http://alcor.concordia.ca/~gpkatch/gdate-algorithm.html
http://alcor.concordia.ca/~gpkatch/gdate-method.html
2

评分人数

    • plp626: 好资料;PB + 10
    • CrLf: 赞!叹为观止PB + 15 技术 + 2

TOP

返回列表