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




    100-2-28 的下一天是 100-2-29 还是 100-3-1

如果是前者,那就不是现在的历法,背后有什么故事么。。。?

TOP

本帖最后由 plp626 于 2012-4-11 20:59 编辑

回复 40# neorobin
  1. @echo off
  2. :date2i <year> <month> <day> // 显示year-month-day 所对应的索引值;
  3. setlcoal&set/a y=%1,m=%2,d=%3
  4. set/a m+=9
  5. set/a m%%=12
  6. set/a y-=m/10
  7. set/a i=365*y+y/4-y/100+y/400+(m*153+2)/5+d-1
  8. endlocal&echo %i%
复制代码
  1. @echo off
  2. :i2date <index> // 显示 %1 所对应的日期 %1 取值范围0~2千万
  3. setlocal&set/a i=%1
  4. set/a y=(i*99+145)/36159
  5. set/a t=i-y*365-y/4+y/100-y/400
  6. set/a "y+=t>>9"
  7. set/a t=i-y*365-y/4+y/100-y/400  
  8. set/a m=(t*5+2)/153
  9. set/a d=t-(m*153+2)/5
  10. set/a d=d+1
  11. set/a y+=(m+2)/12
  12. set/a m=(m+2)%%12+1
  13. endlocal&echo %y%-%m%-%d%
复制代码
在索引号转日期的函数i2date中,(i*99+145)/36159
这个组合保证 [0~21691753)以内的索引;即[0-3-1 ~ 59208-9-2);之间的正确性;

(i*4+999)/1461 这个组合可以保证 [0~12200561 )以内的索引; 即[0-3-1 ~ 33404-3-1)之间的所有日期;

进一步发现
i*33/12053
这个组合可以保证[0~65075232) 以内;即[0-3-1 ~ 177625-3-7) 之间的所有日期;
error: /65075263:177625/ 9/ 9

以上结论只代表在假定函数date2index正确的情况下,没有报错的测试结果;

如果正确,date2index和 index2date两个函数轻松搞定了有关时间日期计算(0-3-1后)的所有问题;

TOP

本帖最后由 plp626 于 2012-4-11 21:02 编辑

C测试,40万年以内测试用时秒杀
  1. //&cls&@type %~fs0|tcc -run -
  2. // 把tcc.exe 和 lib\msvcrt.def 放在当前目录下;双击本脚本解释执行下面C代码
  3. int i2date(int i, int *, int *, int *);
  4. int date2i(int, int, int);
  5. /*
  6. * 把0-3-1作为参考日历{0-0-0}的0号索引,后面依次类推;
  7. * 参考日历月份数为 0,1,2,...,11;
  8. * 11月分闰月和平月;当所在年份+1为闰年时为闰月;
  9. * 参考日历日期数为 0,1,...,30;
  10. * 大月最大偏移30,小月最大偏移29;
  11. * 11月中,闰月最大偏移28;平月最大偏移27;
  12. */
  13. int main(){
  14. int i,j;
  15. int y,t,m,d;
  16. for (i=0; i<1461*100000; i++){ // 0 ~ 40万年索引号
  17. i2date(i,&y,&m,&d);
  18. j=date2i(y,m,d);
  19. if (i!=j) { // 测试,寻找不对称转换的具体值。。
  20. printf("/%5d:%4d/%2d/%2d\n",i,y,m,d);
  21. getchar();
  22. }
  23. }
  24. return 0;
  25. }
  26. int i2date(int i, int *year, int *month, int *day){ // 索引转日期
  27. int t,y,m,d;
  28. y=(i*4+999)/1461;          // 1/365.2425的前6位小数最佳有理逼近
  29. // y=(i*99+145)/36159;
  30. // y=i*33/12053;            //1/365.2425的前9位小数最佳有理逼近
  31. t=i-y*365-y/4+y/100-y/400;
  32. y+=t>>9; // 获得参考年份;若t为负数,将参考年减1
  33. t=i-y*365-y/4+y/100-y/400;  // 获得参考年0月0日的偏移数
  34. m=(t*5+2)/153; // 获得参考月份
  35. d=t-(m*153+2)/5; // 获得参考日期
  36. d=d+1; // 获得实际日期
  37. y+=(m+2)/12; // 获得实际年份
  38. m=(m+2)%12+1; // 获得实际月份
  39. *year=y;
  40. *month=m;
  41. *day=d;
  42. return 0;
  43. }
  44. int date2i(int y, int m, int d){// 日期转索引
  45. int i;
  46. m+=9;
  47. m%=12;
  48. y-=m/10;
  49. //上面三句做平移取模,3月作为当年的0月;2月作为上1年的11月;
  50. i=365*y+y/4-y/100+y/400+(m*153+2)/5+d-1;
  51. // 11月初的偏移为337;337/11≈153/5;
  52. // (m*153+2)/5 将 {0,1,2,...,11}映射为{0,31,61,...,337}
  53. return i;
  54. }
复制代码
可是,这样的对称测试 有意义吗? 即使在date2index正确的条件下,也存在index2date出错;

即使date2index正确,date2index它也只是保证正确的日期生成正确的索引;
错误的日期也有可能生成正确的索引;2002-2-29号是错的,但它在意义上式2002-3-1;生成的索引也是2002-3-1日的索引;
如此以来,还需验证index2date生成正确的日期。。。

TOP

进一步测试发现,i2date函数很是问题;

bug丛生;

问题出在 i2date函数的第一句; 不是随便可以精简的。。。

TOP

回复 48# plp626

这里不考虑 terse 提出的历法变化问题,

42 楼给出的测试方式是 假定一个 简单易懂易实现的 日期生成 算法 是正确的, 用它从 某个日期 开始依次生成某范围内所有后续的日期.
这样生成的一个日期序列的序号对应着整数序列的一部分,

index = date2index(测试起始日期)
loop (简单算法生成 y.m.d,  直到测试终止日期, (y.m.d)++) {
  y.m.d   ->  i = date2index(y.m.d)   ->   yy.mm.dd = index2date(i)
  上过程中: (i ≠ index)  或者 (yy.mm.dd ≠ y.m.d) 即发现错误
  index++
}

bat 测试确实很慢, 还好没事开着不管它, 写成高级语言的也容易, 下面是已经完成测试过的 bat:
  1. @echo off & setlocal enabledelayedexpansion
  2. REM Index := date2index(1.1.1);
  3. set /a "y=1,m=1,d=1"
  4. set /a "m+=9, m%%=12,y-=m/10, i=365*y + y/4 - y/100 + y/400 + (m*306 + 5)/10 + d - 1"
  5. set /a "Index=i"
  6. for /l %%y in (1 1 20000) do (
  7.   set "y=%%y"
  8.   set /a "leap = (^!(%%y %% 4) & ^!^!(%%y %% 100)) | ^!(%%y %% 400)"
  9.   if "!leap!"=="1" (set "eFeb=29") else set "eFeb=28"
  10.   for %%t in ("1 31" "2 !eFeb!" "3 31" "4 30" "5 31" "6 30" "7 31" "8 31" "9 30" "10 31" "11 30" "12 31") do (
  11.     for /f "tokens=1,2" %%m in ("%%~t") do (
  12.       set "m=%%m"
  13.       for /l %%d in (1 1 %%n) do (
  14.         set "d=%%d"
  15.         REM testing...
  16.         REM if date2index(y.m.d) <> Index (
  17.           REM error
  18.         REM )
  19.         REM if index2date(date2index(y.m.d)) <> y.m.d (
  20.           REM error
  21.         REM )
  22.         REM Index += 1;
  23.         set /a "m+=9, m%%=12,y-=m/10, i=365*y + y/4 - y/100 + y/400 + (m*306 + 5)/10 + d - 1"
  24.         if "!i!" neq "!Index!" ((echo error at %%y.%%m.%%d date2index(y.m.d^)=!i!^<^> !Index!) & pause)
  25.         set /a "y=(i*99+145)/36159,y+=i-365*y-y/4+y/100-y/400>>31,d=i-365*y-y/4+y/100-y/400,m=(5*d+2)/153,d+=1-(153*m+2)/5,y+=m/10,m=(m+2)%%12+1"
  26.         set /a "error=(y-%%y)|(m-%%m)|(d-%%d)"
  27.         if "!error!" neq "0" ((echo error at %%y.%%m.%%d index2date(date2index(y.m.d^)^)=!y!.!m!.!d!) & pause)
  28.         echo %%y.%%m.%%d  Index=!Index!
  29.         set /a "Index+=1"
  30.       )
  31.     )
  32.   )
  33. )
  34. pause
  35. exit
复制代码

TOP

本帖最后由 neorobin 于 2012-4-11 23:02 编辑

回复 49# plp626

31 楼, 我做了年份估值误差范围测试, 如果估值函数在给定计算范围内的 估值误差范围 跨过 两个整数(比如 [-0.1,1.3] 跨过了 0 和 1 两个整数), 就不利于后续代码, 后面年份的误差调整就不是简单的只作出 要么不变, 要么加1(或者减1) 这样的方式了, 因为 跨过两个整数 意味着误差的整数绝对值跨度将达到 2.

TOP

回复 46# plp626
100-2-28 的下一天是 100-2-29
这里说的公元100年2月应该有29天
前面我提到 公元1582年10月4号前是每4年一闰  不管年份是否可被100或400整除
1582年10月由格勒哥里十三世改革成为格勒哥里历,取消1582年10月5日至1582年10月14日这10日及取消400年内00年尾的3个闰年,使一年的平均日数变成365.2425日,更接近于准确的回归年365.2422日。
格勒哥里历(也就是现在大多使用的):
每4年置闰年一次,闰年366日,二月29日.凡年份数可被4 整除者,置闰. 如1960,1996等.凡年份数可被100整除者,不置闰, 如1800,1900.凡年份数可被400整除者,置闰, 如2000,2400.

TOP

回复 52# terse


不同年份阶段,要不同的历法,    太复杂了; 很不好计算;

反正matlab里面100-2-28的下一天是100-3-1;
  1. >> datestr(36584,26)
  2. ans =
  3. 0100/02/28
  4. >> datestr(36585,26)
  5. ans =
  6. 0100/03/01
  7. >>
复制代码

TOP

本帖最后由 neorobin 于 2012-4-11 23:10 编辑

回复 53# plp626


    用个能算到 公元 3000 年的历法就蛮足够了, 往过去可以算 100 年也就可以了, 我们不考古, 呵呵,  我用过最大的用途就是看下出生日期什么的.


51 楼纠正一下, 应说成 整数误差绝对值 跨度 将达到 2: 例如, 应该得到的 年份是 2012, 但误差范围可能出现 3 种(据算法择其中一种)可能的区间 [2010,2012], [2011,2013], [2012,2014], 尽管这个误差正峰值和负峰值并不会发生在对同一个年份的估值上, 但代码上总是要对这两个峰值作统一处理的.

TOP

i2date中,
  1. y=(i*99+145)/36159;
复制代码
通过9999年以内的索引转日期测试;
我用matlab生成标准日期文件69M+;
再用测试代码用C生成了标准日期文件69M+;
两个文件md5值相同均为 a6740e69a45b825334b8c4c4c986fe2f;

标准打印格式:

fid 模式wt;
fprintf(fid,"%7d:%.4d/%.2d/%.2d\n",i,y,m,d);
末行 3652060:9999/03/02

索引范围 0~3652060 [0-3-1 ~ 9999-3-2]

TOP

针对1582 10 4 ----1582 10 15 的测试
  1. @echo off&setlocal enabledelayedexpansion
  2. (FOR /l %%i in (2298883 1 2299603) DO (
  3.     set JD=%%i
  4.     IF !JD! GEQ 2299161 set /a "JD+=1+(JD*100-186721625)/3652425-(JD*100-186721625)/3652425/4"
  5.     set /a "B=JD+1524,Y=(B*100-12210)/36525,D=36525*Y/100"
  6.     set /a "M=(B-D)*100/3061,D=B-D-3061*M/100,M=(M-2)%%12+1,Y-=4715+^!(2/M)"
  7.     set "str=!Y!:!M!:!D!"
  8.     set /a "M=(M+9)%%12+3,Y-=M/13,JD=36525*(Y+4716)/100+3061*(M+1)/100+D-1524"
  9.     REM 1582年10月4日后重新计算闰年,先前每4年一闰.
  10.     set /a "JD+=(2-Y/100+Y/400)*^!(2299161/JD)"
  11.     ECHO !str! !JD!
  12. ))>Yjd.txt
  13. start "" "Yjd.txt"
  14. PAUSE
复制代码
  1. @echo off&setlocal enabledelayedexpansion
  2. for %%i in (31 28 31 30 31 30 31 31 30 31 30 31) do set /a N+=1&set "M_!N!=%%i"
  3. set JDX=2298883
  4. for /l %%i in (1582 1 1583) do (
  5.      IF %%i gtr 1582 (
  6.         set /a "N=^!(%%i%%4)^^^!(1582/%%i)&^!(%%i%%400)^^^!(1582/%%i)&^!(%%i%%100)"
  7.      ) else set /a "N=^!(%%i%%4)"
  8.      set /a M_2=28+N
  9.      for /l %%j in (1 1 12) do (
  10.          set /a "M=(%%j+9)%%12+3,Y=%%i-M/13"
  11.          for /l %%k in (1 1 !M_%%j!) do (
  12.              set /a "JD=36525*(Y+4716)/100+3061*(M+1)/100+%%k-1524,JDX+=1"
  13.              set /a "JD+=(2-Y/100+Y/400)*^!(2299161/JD)"  2>nul
  14.              IF !JDX! NEQ !JD! set ERR=!err!$err:%%i:%%j:%%k !JD! !JDX!$
  15.              REM 1582年10月4日后初始日须减10天
  16.              if "%%i:%%j:%%k" == "1582:10:5" set/a JDX-=10
  17.          )
  18.      )
  19. )
  20. IF DEFINED ERR echo !err:$=^
  21. !
  22. ECHO !JD! !JDX!
  23. pause
复制代码

TOP

回复 51# neorobin


    我相信那个比值 (33*i+x)/12053可代替 (i*99+145)/36159;且范围不会大幅缩减;

找3000年以内的;我考虑 (4*i+x)/1461

TOP

本帖最后由 neorobin 于 2012-4-11 23:32 编辑

回复 57# plp626


    我测试的结果是 [1,3000] 内,  (i * 4 + 93) / 1461 就可行

(i * 4 + 0) / 1461
The pOffsMin is: -0.062971;  the pOffsMax is: 0.000000
The pMin is: 3000;  the pMax is: 4
pOffsMin = -0.062971,  pOffsMax + 365*4 / 1461 = 0.999316

0.062971 * 1461 = 92.000631

(i * 4 + 93) / 1461
The pOffsMin is: 0.000000;  the pOffsMax is: 0.063655
The pMin is: 4;  the pMax is: 4
pOffsMin = 0.000000,  pOffsMax + 365*4 / 1461 = 1.062971

TOP

你测试 (i * 4 + 93) / 1461 兼容的最大范围是?

TOP

回复 59# plp626

测试代码确实有问题, 是记录 误差最小值的一个变量的初始值设小了,  但 93 这个常数还是碰对了

修改后:
   
93
The pOffsMin is: 0.000684;  the pOffsMax is: 0.063655
The pMin is: 3000;  the pMax is: 4
pOffsMin = 0.000684,  pOffsMax + 365*4 / 1461 = 1.062971



0
The pOffsMin is: -0.062971;  the pOffsMax is: 0.000000
The pMin is: 3000;  the pMax is: 4
pOffsMin = -0.062971,  pOffsMax + 365*4 / 1461 = 0.999316

0.062971 * 1461 = 92.000631

第一次出错的发生:
3002.2.28  Index=1096456
error at 3002.3.1 index2date(date2index(y.m.d))=3002.2.29

TOP

返回列表