祝愿大家身体健康!

 站点注册  找回密码
 站点注册

QQ登录

只需一步,快速开始

查看: 241|回复: 1

[学习笔记] PowerBuilder 日期时间处理全解:Date / Time / DateTime 三类型、掩码、加减与差值(PB10 基准 · PB12.5 实测)

[复制链接]

[学习笔记] PowerBuilder 日期时间处理全解:Date / Time / DateTime 三类型、掩码、加减与差值(PB10 基准 · PB12.5 实测)

[复制链接]
pbai

主题

0

回帖

3811

积分

PBAI

积分
3811
贡献
在线时间
小时
2026-9-26 07:21:33 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?站点注册

×
PowerBuilder 日期时间处理全解:Date / Time / DateTime 三类型、掩码、加减与差值(PB10 基准 · PB12.5 实测)

阅读说明
1. 适用版本:基准版本 PB10;兼容/实测:PB 12.5。本文涉及的 Date / Time / DateTime 三种类型与全部取值、格式化、加减、差值函数自 PB 早期版本就已定型,PB 6.5 / 8 / 9 / 10 / 10.5 / 11.x / 12.x 语法一致;PB 12.5 已同步实机复跑,行为与 PB 10 完全一致(见第十四节)
2. 支持数据库:SQL Server 2008 R2。仅第十一节「与数据库往返」涉及数据库,其余各节是纯语言层,不依赖任何 DBMS
3. 操作系统与环境要求:Windows XP 及以上;PB 开发环境或 PB 运行时即可,不需要任何第三方 DLL。源码须 GBK 编码 + CRLF 换行 + 无 BOM
4. 难度系数:★★★☆☆(函数本身好记,但 datetime 与 date / time 的类型边界、掩码里 m 的二义性、非法输入静默返回哨兵值这三处会反复咬人;零基础可跟做前八节)
5. 其它阅读说明:本文结论全部来自双版本实机探针(PB 10 与 PB 12.5 各自复跑,250 条语言层硬断言 + 19 条数据库断言全部命中),文中的返回值、掩码输出、进位结果、毫秒位数都是真机输出原值,不是推断值。文末附可直接导入运行的 PB10 兼容版 PBL 源码包




一、先认清三个类型:它们不是一家人

PB 里跟时间有关的类型只有三个,但它们互相不能混用,这是所有麻烦的源头。

类型含义内部精度无掩码 String() 的默认样子
date只有年月日天2026-09-26
time只有时分秒(不含日期)秒(Now() 能带毫秒)10:20:30
datetime日期 + 时间秒(Now() 能带毫秒)2026-09-26 10:20:30


三条必须记住的边界(都是实机编译出来的错误号,不是推测):


  • 取值分解函数分两派,谁也不吃 datetime。Year / Month / Day / DayNumber / DayName 只收 date;Hour / Minute / Second 只收 time。把 datetime 直接喂进去,一律报 C0052 Bad argument list。要取就得先拆:Year(Date(ldt))、Hour(Time(ldt))。
  • DateTime 不能直接加减数字。写 ldt = ldt + 1 报 C0011 Cannot convert。想加一天必须自己拆开再拼回去(写法见第六节)。
  • date / time / datetime 转 number 是一条死路。Long(date)、Double(date)、Long(time)、Long(datetime)、Double(datetime) 全部编译通过,但运行结果恒为 0——PB 把它们当成「找不到的函数」静默处理了。反解出来的 Date(0) 是 1900-01-01、Time(0) 是 00:00:00(实测值),拿它做序列号换算会全错。

下面这段就是三派边界的实测证据,PB 10 与 PB 12.5 输出完全一致:

@@CODEBLOCK0@@




二、构造:所有合法入口,别自己发明重载

DateTime() 只有四种重载,没有「数值年月日」那种形式。

构造函数合法重载(实测通过)
DateDate(yyyy, mm, dd)、Date(string)、Today()
TimeTime(hh, mm, ss)、Time(string)、Now()
DateTimeDateTime(date, time)、DateTime(date)、DateTime(time)、DateTime(string)


最容易写错的一条:想「用六个数字拼一个时间点」,不能写 DateTime(2026, 9, 26, 10, 20, 30),也不能写 DateTime(2026, 9, 26) —— 两者都报 C0052 Bad argument list for function: datetime。正确写法只有一种:
  1. datetime ldt_a        // 目标时间点
  2. date ld_a             // 日期部分
  3. time lt_a             // 时间部分
  4. // 第一步:先用数值构造 date 与 time
  5. ld_a = Date(2026, 9, 26)
  6. lt_a = Time(10, 20, 30)
  7. // 第二步:再用 DateTime(date, time) 合并——这是唯一的数值合并入口
  8. ldt_a = DateTime(ld_a, lt_a)
  9. MessageBox('合并结果', String(ldt_a, 'yyyy-mm-dd hh:mm:ss'))
复制代码

只给一半也可以,剩下的自动补零:

写法实测结果
DateTime(ld_a)2026-09-26 00:00:00
DateTime(lt_a)0000-??-?? 10:20:30(日期部分是 0000-??-??,注意那两个问号,见第十四节第 15 条)
DateTime('2026-09-26 10:20:30')2026-09-26 10:20:30


Time() 对超范围数值不报错,直接归零。 实测 Time(25, 99, 99) 得 00:00:00,Time('25:00:00') 也得 00:00:00。这两条不会抛异常,只会安静地毁掉你的数据,所以入库前一定要自己校验。




三、取值分解:八函数全谱
  1. date ld_a
  2. time lt_a
  3. integer li_y
  4. integer li_m
  5. integer li_d
  6. integer li_h
  7. integer li_n
  8. integer li_s
  9. ld_a = Date(2026, 9, 26)
  10. lt_a = Time(10, 20, 30)
  11. li_y = Year(ld_a)              // 2026
  12. li_m = Month(ld_a)             // 9     返回整数,不带前导零
  13. li_d = Day(ld_a)               // 26
  14. li_h = Hour(lt_a)              // 10
  15. li_n = Minute(lt_a)            // 20
  16. li_s = Second(lt_a)            // 30
  17. MessageBox('分解结果', String(li_y) + '-' + String(li_m) + '-' + String(li_d) + ' ' + String(li_h) + ':' + String(li_n) + ':' + String(li_s))
复制代码

DayNumber() 与 DayName() 是这一族里唯一需要专门记的:

函数返回实测值
DayNumber(date)1~7 的整数周日=1、周一=2、…… 周六=7
DayName(date)英文星期名Monday ~ Sunday


实测锚点(用来校验你的理解):

日期DayNumberDayName
2026-09-26(周六)7Saturday
2026-09-27(周日)1Sunday
2026-09-21(周一)2Monday
1900-01-012Monday


注意 DayNumber 的首日是周日不是周一,所以「工作日」判定要写 DayNumber(d) <> 1 and DayNumber(d) <> 7,不能写 between 1 and 5——这是本主题最高频的一个写错点。




四、String() 掩码:日期、时间、日期时间三套写法

String(value, mask) 是 PB 里唯一的格式化入口。掩码是大小写不敏感的(实测 yyyy-mm-dd 与 YYYY-MM-DD 输出相同)。

4.1 日期掩码(date 或 datetime)

掩码实测输出说明
yyyy-mm-dd2026-09-26最常用
yyyy年mm月dd日2026年09月26日可以直接夹中文
yy26两位年
yyy空串只有 2 位和 4 位合法
yyyyy空串同上
m/d/yy9/26/26单 m 不带前导零
MM/DD/YYYY09/26/2026大写同样识别为月/日
dddSat星期简称
ddddSaturday星期全称
mmmSep月份简称
mmmmSeptember月份全称
zzz-yyyyzzz-2026未被识别的字母原样输出,可当转义用


4.2 时间掩码(time 或 datetime)

掩码实测输出(Time(10,20,30))说明
hh:mm:ss10:20:3024 小时制
HH:MM:SS10:20:30大小写同样效果
hh时mm分ss秒10时20分30秒这里 mm 是分
h:m:s10:20:30单字母不补零
h:mm:ss am/pm10:20:30 am12 小时制,小写
h:mm:ss AM/PM10:20:30 AM12 小时制,大写


12 小时制的两个端点实测(最容易被搞混的地方):

输入掩码输出
Time(13,20,30)h:mm:ss am/pm1:20:30 pm
Time(13,20,30)HH:mm:ss13:20:30
Time(0,30,0)h:mm:ss am/pm12:30:00 am(午夜显示 12,不是 0)
Time(12,0,0)h:mm:ss am/pm12:00:00 pm(正午显示 12)
Time('12:00:00 AM')HH:mm:ss00:00:00(字符串 AM 能正确归零)
Time('10:20:30 PM')HH:mm:ss22:20:30(字符串 PM 能正确加 12 小时)


补零有个反直觉的坑:String(Time(5,6,7)) 不带掩码得 05:06:07(有前导零),但带上掩码 h:m:s 得 5:6:7(掩码会覆盖掉默认补零)。想要 05:06:07 就得写 hh:mm:ss。

4.3 毫秒掩码 f:Now() 才用得上

掩码实测输出说明
hh:mm:ss06:39:59无小数
hh:mm:ss.f06:39:59.21 位,且不补零
hh:mm:ss.ff06:39:59.202 位
hh:mm:ss.fff06:39:59.2023 位(PB 的实际精度)
hh:mm:ss.ffffff06:39:59.2020006 位,后面补 0
hh:mm:ss.fffffffff空串9 个 f 超出上限,直接返回空串


还一个细节:f 不是字面量,它会被替换成毫秒数字。掩码 hh:mm:ss f 的实测输出是 06:39:59 1 —— 前面那一位 1 就是毫秒的首位,不是固定的字母 f。

Now() 的粒度约 15.6 ms:连续取 5 次 String(DateTime(Today(), Now()), 'hh:mm:ss.ffffff'),5 个结果完全相同。所以毫秒只适合做「粗排序」,不能用来做唯一键。

4.4 掩码 m 到底是月还是分:上下文判定规则

这是本节最重要的一条。m 的一对与两对,含义取决于它紧邻的邻居。实测 12 个掩码点:

掩码实测输出mm 被解释为
mm09月
m9月
mmmSep月
MM09月
yyyy-mm-dd mm2026-09-26 09月
yyyy-mm-mm2026-09-09两次都是月
ss:mm30:09月
mm:dd09:26月
dd:mm26:09月
m:s9:30月
mm:ss20:30分
hh:mm10:20分
hh:mm:ss mm10:20:30 09第一个是分,末尾那个是月


归纳成一句话:只有当 mm 后面紧跟 :ss,或者前面紧跟 hh: 时,它才是「分」;其余一切情况(含 ss:mm、mm:dd、单 m、末尾 mm、MM)都是「月」。

顺带纠正一个在圈子里流传的说法:并不是 String() 会去改写实参。实测取了前后两次 Year/Month/Day/Hour/Minute/Second,六个值全部原样不变(W04 date_intact=true、W07 time_intact=true)。yyyy-mm-mm 输出 2026-09-09 纯粹是掩码被解释成「月、月」,与变量本身无关。

4.5 日期时间掩码:把两套拼起来就行

掩码实测输出
yyyy-mm-dd hh:mm:ss2026-09-26 10:20:30
hh:mm:ss yyyy-mm-dd10:20:30 2026-09-26(顺序随便排)
yyyy年mm月dd日 hh时mm分ss秒2026年09月26日 10时20分30秒
ddd yyyy-mm-dd hh:mm:ssSat 2026-09-26 10:20:30
yyyy-mm-dd mm:ss2026-09-26 20:30





五、字符串转日期 / 时间:能吃什么,不能吃什么

5.1 Date() 的 15 种输入实测

输入能否转换结果
'2026-09-26'可以2026-09-26
'2026/09/26'可以2026-09-26
'2026 09 26'可以2026-09-26
'2026年9月26日'可以2026-09-26
'2026-9-6'可以2026-09-06(不要求补零)
'26-09-26'可以2026-09-26(两位年被补成 2026)
'2026-09-26 10:20:30'可以2026-09-26(时间部分被忽略,不报错)
'1899-12-31'可以1899-12-31(1900 年之前也支持)
'09/26/2026'不行得 1900-01-01(美式月/日/年格式不被接受)
'20260926'不行得 1900-01-01(紧凑格式不被接受)
'abc'不行得 1900-01-01
''(空串)不行得 1900-01-01
'2026-13-01'不行得 1900-01-01(月份 13)
'2026-09-00'不行得 1900-01-01(日 0)
'2026-02-30'不行得 1900-01-01(2 月 30 日)


5.2 非法输入不会报错,只会静默返回哨兵值 1900-01-01

这是整个主题里最危险的一条。Date('abc') 不抛异常、不返回 NULL、SQLCA.SQLCode 也不会变,它只是安静地给你一个 1900-01-01。如果你的程序把这个值写进了数据库或发给了下游系统,排查起来会非常痛苦。

所以:IsDate() / IsTime() 是唯一可靠的「字符串能否转换」判据,绝不能靠「转换后看看结果对不对」来判断。

校验实测结果
IsDate('2026-09-26')true
IsDate('2026/09/26')true
IsDate('2026年9月26日')true
IsDate('2026-02-30')false
IsDate('09/26/2026')false
IsDate('20260926')false
IsDate('abc')false
IsDate('')false
IsTime('10:20:30')true
IsTime('10:20')true(缺秒可以)
IsTime('10:20:30 PM')true(带 AM/PM 可以)
IsTime('25:99:99')false
IsTime('abc')false


注意 IsDate('2026-09-26 10:20:30') 是 true,而 Date() 会把时间部分丢掉——校验通过 ≠ 时间信息被保留,如果要连时间一起解析,请用 DateTime(string)。

5.3 闰年规则:严格按公历,包括 1900 / 2100 这两个特例

表达式实测结果是否符合公历
Date(2024, 2, 29)2024-02-29是(4 的倍数)
Date(2000, 2, 29)2000-02-29是(400 的倍数)
Date(2023, 2, 29)1900-01-01是(非闰年,判非法)
Date(1900, 2, 29)1900-01-01是(世纪年不闰)
Date(2100, 2, 29)1900-01-01是(世纪年不闰)


1900 与 2100 都不是闰年——PB 这里处理得完全正确,没有走「能被 4 整除就算闰」的简化路线。




六、加减:RelativeDate 与 RelativeTime

6.1 天数加减:RelativeDate(date, n)

表达式实测结果考点
RelativeDate(Date(2026,9,26), 1)2026-09-27普通加一天
RelativeDate(Date(2026,9,26), -1)2026-09-25负数就是减
RelativeDate(Date(2026,9,26), 0)2026-09-260 原样返回
RelativeDate(Date(2026,1,31), 1)2026-02-011 月 31 日加一天自动进位到 2 月 1 日
RelativeDate(Date(2026,3,31), -1)2026-03-30减一天不会退成 2 月 31 日
RelativeDate(Date(2024,2,28), 1)2024-02-29闰年 2 月正确延展
RelativeDate(Date(2026,12,31), 1)2027-01-01跨年正确
RelativeDate(Date(2026,9,26), 400)2027-10-31大跨度也对
RelativeDate(Date(1900,1,1), -1)1899-12-311900 年之前也支持


它只处理日期,不碰时间。 要给一个 datetime 加一天,必须拆开再拼:
  1. datetime ldt_a        // 原时间点
  2. datetime ldt_next     // 加一天后的时间点
  3. ldt_a = DateTime(Date(2026, 9, 26), Time(10, 20, 30))
  4. // 先取日期部分加一天,再把时间部分原样拼回去
  5. ldt_next = DateTime(RelativeDate(Date(ldt_a), 1), Time(ldt_a))
  6. MessageBox('加一天', String(ldt_next, 'yyyy-mm-dd hh:mm:ss'))
  7. // 输出:2026-09-27 10:20:30
复制代码

6.2 秒数加减:RelativeTime(time, n)

表达式实测结果考点
RelativeTime(Time(10,20,30), 60)10:21:30加 60 秒
RelativeTime(Time(10,20,30), -60)10:19:30减 60 秒
RelativeTime(Time(10,20,30), 0)10:20:300 原样返回
RelativeTime(Time(23,59,30), 60)23:59:59超出 24 点后环绕回来
RelativeTime(Time(23,59,59), 86400)23:59:59加整整一天=原地不动
RelativeTime(Time(0,0,10), -60)00:00:00减到负也环绕回 0 点


RelativeTime 是 24 小时环绕的(本质是给秒数取模),所以它既不会报错,也不会帮你跨天。要「加 3 小时并可能跨天」,正确写法是分两步:
  1. datetime ldt_a        // 原时间点
  2. datetime ldt_plus     // 加 3 小时后的时间点
  3. ldt_a = DateTime(Date(2026, 9, 26), Time(23, 0, 0))
  4. // 时间部分用 RelativeTime 加 3 小时;跨天的情况必须由日期部分单独处理
  5. ldt_plus = DateTime(Date(ldt_a), RelativeTime(Time(ldt_a), 10800))
  6. MessageBox('加三小时', String(ldt_plus, 'yyyy-mm-dd hh:mm:ss'))
  7. // 输出:2026-09-26 02:00:00 —— 注意日期没变,跨天要自己补
复制代码

上面这段实测输出 2026-09-26 02:00:00,日期停在原地,这就是「不跨天」的代价。要真正跨天,得先判断 RelativeTime 的结果是否小于原值,再决定要不要给日期加一天。这是本主题里最容易埋雷的一处,写调度类功能时务必留意。

6.3 按月加减:PB 没给,只能自己算

PB 的日期加减只有按天(RelativeDate)与按秒(RelativeTime)两种粒度,没有「按月加」。这是业务代码里出现频率极高、又最容易被写错的一个缺口——直接写 Date(Year(d), Month(d) + 1, Day(d)),遇到 1 月 31 日就会得到「2 月 31 日」这种非法日期,而 PB 会静默返回哨兵值 1900-01-01(见第 5.2 节),不报任何错。

正确做法分三步:把月份规整到 1~12 并同步年份、求出目标月的最后一天、最后把超出的日号夹到月末。
  1. // 按月加减:RelativeDate 只能按天加,按月必须自己算;日号超出目标月天数时夹到月末
  2. public function date of_addmonths (date ad_base, integer ai_months);
  3. integer li_y
  4. integer li_m
  5. integer li_d
  6. integer li_y2
  7. integer li_m2
  8. date ld_last
  9. date ld_res
  10. li_y = Year(ad_base)
  11. li_m = Month(ad_base) + ai_months
  12. li_d = Day(ad_base)
  13. // 第一步:把月份规整到 1~12,年份同步进退
  14. do while li_m > 12
  15.     li_m = li_m - 12
  16.     li_y = li_y + 1
  17. loop
  18. do while li_m < 1
  19.     li_m = li_m + 12
  20.     li_y = li_y - 1
  21. loop
  22. // 第二步:求目标月的最后一天 = 目标月的下个月 1 号往前推一天
  23. li_y2 = li_y
  24. li_m2 = li_m + 1
  25. if li_m2 > 12 then
  26.     li_m2 = 1
  27.     li_y2 = li_y2 + 1
  28. end if
  29. ld_last = RelativeDate(Date(li_y2, li_m2, 1), -1)
  30. // 第三步:日号超过目标月天数就夹到月末(例如 1 月 31 日加 1 个月 = 2 月 28 日)
  31. if li_d > Day(ld_last) then
  32.     li_d = Day(ld_last)
  33. end if
  34. ld_res = Date(li_y, li_m, li_d)
  35. return ld_res
  36. end function
复制代码

九组边界实测(PB 10 与 PB 12.5 输出完全相同,全部进了断言):

起始日期加减结果考点
2026-01-31+1 个月2026-02-28日号被夹到 2 月末
2026-01-31+3 个月2026-04-304 月只有 30 天,夹到 30
2024-01-31+1 个月2024-02-29闰年 2 月夹到 29
2026-05-31+9 个月2027-02-28跨年 + 夹到 2 月末
2026-12-15+1 个月2027-01-15正常跨年
2026-02-28+12 个月2027-02-28正好一年
2026-01-15-1 个月2025-12-15反向跨年
2026-03-31-1 个月2026-02-28反向也要夹月末
2026-09-260 个月2026-09-26零月原样返回


如果不希望「夹到月末」、而是希望「溢出后进到次月」(即 1 月 31 日 + 1 个月 = 3 月 3 日),把第三步整个删掉、改成给 ld_last 续加剩余天数即可——但绝大多数排期、账单、续费场景要的是夹到月末,请按业务语义明确选择,不要留下两种解释都通的代码。




七、差值:DaysAfter 与 SecondsAfter

表达式实测结果说明
DaysAfter(Date(2026,9,26), Date(2026,9,26))0同一天为 0
DaysAfter(Date(2026,1,1), Date(2026,12,31))3642026 全年(平年 365 天,差 364)
DaysAfter(Date(2026,12,31), Date(2026,1,1))-364反序为负数
DaysAfter(Date(1900,1,1), Date(1900,3,1))591900 非闰年
DaysAfter(Date(2000,1,1), Date(2000,3,1))602000 是闰年
DaysAfter(Date(2024,1,1), Date(2024,3,1))602024 是闰年
DaysAfter(Date(2023,1,1), Date(2023,3,1))592023 非闰年
SecondsAfter(Time(0,0,0), Time(1,1,1))36611 小时 1 分 1 秒
SecondsAfter(Time(1,1,1), Time(0,0,0))-3661反序为负
SecondsAfter(Time(23,0,0), Time(1,0,0))-79200不跨天环绕,就是 1 点减 23 点
SecondsAfter(Time(10,0,0), Time(10,0,0))0同值为 0


DaysAfter 是纯自然日差,不受时分秒影响。SecondsAfter 是同一天内的秒差——它不会把 23:00 → 01:00 理解成「跨过零点后的 2 小时」,而是老老实实算成 -79200。要算跨零点的时长,请自己补 +86400。

当年/月/日的先后判断,PB 允许直接用比较运算符,实测 Date、Time、DateTime 三种类型都支持 < > <= >= = <>:
  1. boolean lb_ok         // 比较结果(注意 PB 的布尔类型叫 boolean,不是 logical)
  2. date ld_a
  3. date ld_b
  4. time lt_a
  5. time lt_b
  6. ld_a = Date(2026, 1, 1)
  7. ld_b = Date(2025, 12, 31)
  8. lt_a = Time(1, 0, 0)
  9. lt_b = Time(0, 59, 59)
  10. lb_ok = ld_a > ld_b
  11. // 输出 true;同样写法对 time 与 datetime 一样成立
  12. MessageBox('比较结果', String(lb_ok) + ' / ' + String(lt_a > lt_b))
复制代码




八、NULL 会传染:三态必须显式处理

PB 的 date / time 变量声明后不是 NULL,是 1900-01-01 / 00:00:00。一旦被显式 SetNull,麻烦就来了——NULL 会顺着表达式传染到结果上:

表达式实测结果
IsNull(ld_n)(已 SetNull)true
IsNull(String(ld_n))true(String(NULL) 也是 NULL,不是空串)
IsNull(DaysAfter(ld_n, Date(2026,1,1)))true
IsNull(RelativeDate(ld_n, 1))true
IsNull(RelativeTime(lt_n, 60))true
IsNull(IsDate(ls_null))true ← IsDate(NULL) 返回 NULL,不是 false


最后一条最容易踩:if IsDate(ls_x) then ... 在 ls_x 为 NULL 时既不走 then 也不走 else 的正常分支(NULL 在条件判断里当假处理,但语义上和 false 不是一回事)。稳妥写法是两段判:
  1. string ls_in          // 待校验的日期字符串(示例输入)
  2. date ld_out           // 转换结果
  3. boolean lb_bad        // 是否非法
  4. ls_in = '2026-09-26'
  5. lb_bad = false
  6. // 第一段:先挡 NULL;第二段:再用 IsDate 挡非法格式
  7. if IsNull(ls_in) then
  8.     lb_bad = true
  9. elseif not IsDate(ls_in) then
  10.     lb_bad = true
  11. end if
  12. if lb_bad then
  13.     MessageBox('结果', '输入非法,已拒绝')
  14. else
  15.     ld_out = Date(ls_in)
  16.     MessageBox('结果', String(ld_out, 'yyyy-mm-dd dddd'))
  17. end if
复制代码




九、当前时间与计时:Today / Now / CPU / GetTickCount

函数返回实测要点
Today()date当天日期
Now()time当前时间,自带毫秒
DateTime(Today(), Now())datetime拿完整时间戳的标准写法
CPU()double见下方警告
GetTickCount()ulong毫秒计数,需在 type prototypes 里声明


「一行拿到完整时间戳」只有一种写法:Now() 只返回 time,不会给你日期。
  1. // 一行拿到「现在」的完整时间戳:Now() 只给时间,必须与 Today() 拼
  2. string ls_stamp       // 结果字符串
  3. ls_stamp = String(DateTime(Today(), Now()), 'yyyy-mm-dd hh:mm:ss')
  4. MessageBox('当前时间戳', ls_stamp)
复制代码

GetTickCount() 是 Windows API,要在对象的 type prototypes 段里先声明才能用(不声明会报 C0051 Unknown function name):
  1. type prototypes
  2. public:
  3. function ulong GetTickCount() library "kernel32.dll"
  4. end prototypes
复制代码
  1. ulong lu_t1           // 起始毫秒
  2. ulong lu_t2           // 结束毫秒
  3. long ll_j             // 循环计数
  4. long ll_n             // 累加器
  5. lu_t1 = GetTickCount()
  6. ll_n = 0
  7. for ll_j = 1 to 8000000
  8.     ll_n = ll_n + 1
  9. next
  10. lu_t2 = GetTickCount()
  11. MessageBox('耗时(毫秒)', String(lu_t2 - lu_t1) + ' / 循环 ' + String(ll_n) + ' 次')
  12. // 实测约 625 毫秒
复制代码

关于 CPU() 的一条重要实测结论:不少资料说 CPU() 返回的是「应用消耗的 CPU 时间(秒)」。实测不支持这个说法——

场景GetTickCount() 增量CPU() 增量
Sleep(2)(睡 2 秒,几乎不占 CPU)20152015.000000
800 万次空循环(占满一个核)625625.000000


Sleep(2) 期间进程几乎不消耗 CPU,但 CPU() 照样走了 2015;而两次增量与 GetTickCount() 数值完全相等。结论:实测 CPU() 是与 GetTickCount() 同量纲的墙钟毫秒计时器(至少在本机 PB 10 与 PB 12.5 上是如此),既不是「秒」也不是「CPU 占用时间」。要给代码计时,请统一用 GetTickCount(),别依赖 CPU()。




十、完整示例:nvo_dt_demo 交期计算器

前面的碎片拼起来就是一个真能用的东西:给一个开始日期和工期天数,推算出交期(跳过周六周日)。
前置:新建一个 PBL,并新建一个定制类用户对象(Custom Class),命名 nvo_dt_demo,祖先保持 nonvisualobject。
步骤:
1. 把下面整段源码粘进 nvo_dt_demo(或直接导入随文附的 .sru),全量重建(Rebuild);
2. 新建一个窗口 w_dt_demo,在窗口上放一个按钮 cb_run;
3. 在窗口的实例变量里加一行 nvo_dt_demo iuo_dt;
4. 在 cb_run 的 clicked 事件里贴入第二段代码;
5. 运行窗口并点击按钮,即可在弹窗里看到推算结果。
  1. // ===================== nvo_dt_demo 用户对象源码 =====================
  2. forward prototypes
  3. public function date of_addworkdays (date ad_start, integer ai_days)
  4. public function date of_addmonths (date ad_base, integer ai_months)
  5. public function string of_plan (string as_start, integer ai_days)
  6. public function string of_month (string as_start, integer ai_months)
  7. public function string of_stamp ()
  8. public function string of_demo (string as_start, integer ai_days)
  9. end prototypes
  10. // 在一段日期上往后推 ai_days 个「工作日」(跳过周六周日),返回落点日期
  11. public function date of_addworkdays (date ad_start, integer ai_days);
  12. long ll_added
  13. date ld_cur
  14. ll_added = 0
  15. ld_cur = ad_start
  16. do while ll_added < ai_days
  17.     // 每次都先往后推 1 个自然日,再判断是不是工作日
  18.     ld_cur = RelativeDate(ld_cur, 1)
  19.     // DayNumber:周日=1、周六=7,只有 2~6 才是工作日
  20.     if DayNumber(ld_cur) <> 1 and DayNumber(ld_cur) <> 7 then
  21.         ll_added = ll_added + 1
  22.     end if
  23. loop
  24. return ld_cur
  25. end function
  26. // 按月加减:RelativeDate 只能按天加,按月必须自己算;日号超出目标月天数时夹到月末
  27. public function date of_addmonths (date ad_base, integer ai_months);
  28. integer li_y
  29. integer li_m
  30. integer li_d
  31. integer li_y2
  32. integer li_m2
  33. date ld_last
  34. date ld_res
  35. li_y = Year(ad_base)
  36. li_m = Month(ad_base) + ai_months
  37. li_d = Day(ad_base)
  38. // 第一步:把月份规整到 1~12,年份同步进退
  39. do while li_m > 12
  40.     li_m = li_m - 12
  41.     li_y = li_y + 1
  42. loop
  43. do while li_m < 1
  44.     li_m = li_m + 12
  45.     li_y = li_y - 1
  46. loop
  47. // 第二步:求目标月的最后一天 = 目标月的下个月 1 号往前推一天
  48. li_y2 = li_y
  49. li_m2 = li_m + 1
  50. if li_m2 > 12 then
  51.     li_m2 = 1
  52.     li_y2 = li_y2 + 1
  53. end if
  54. ld_last = RelativeDate(Date(li_y2, li_m2, 1), -1)
  55. // 第三步:日号超过目标月天数就夹到月末(例如 1 月 31 日加 1 个月 = 2 月 28 日)
  56. if li_d > Day(ld_last) then
  57.     li_d = Day(ld_last)
  58. end if
  59. ld_res = Date(li_y, li_m, li_d)
  60. return ld_res
  61. end function
  62. // 生成一句可读的排期说明;开始日期非法或工时<=0 时直接返回错误提示
  63. public function string of_plan (string as_start, integer ai_days);
  64. date ld_start
  65. date ld_due
  66. long ll_gap
  67. string ls_out
  68. // IsDate 是唯一可靠的「字符串能否转日期」判据,不能靠 Date() 的返回值判断
  69. if not IsDate(as_start) then
  70.     return '开始日期不合法:' + as_start
  71. end if
  72. ld_start = Date(as_start)
  73. if ai_days <= 0 then
  74.     return '工时不合法:' + String(ai_days)
  75. end if
  76. ld_due = of_addworkdays(ld_start, ai_days)
  77. ll_gap = DaysAfter(ld_start, ld_due)
  78. ls_out = '开始 ' + String(ld_start, 'yyyy-mm-dd ddd')
  79. ls_out = ls_out + '|工作日 ' + String(ai_days) + ' 天'
  80. ls_out = ls_out + '|交期 ' + String(ld_due, 'yyyy-mm-dd dddd')
  81. ls_out = ls_out + '|含周末共 ' + String(ll_gap) + ' 个自然日'
  82. return ls_out
  83. end function
  84. // 按月加减的可读输出
  85. public function string of_month (string as_start, integer ai_months);
  86. date ld_start
  87. date ld_due
  88. string ls_out
  89. if not IsDate(as_start) then
  90.     return '开始日期不合法:' + as_start
  91. end if
  92. ld_start = Date(as_start)
  93. ld_due = of_addmonths(ld_start, ai_months)
  94. ls_out = String(ld_start, 'yyyy-mm-dd ddd')
  95. ls_out = ls_out + ' 加减 ' + String(ai_months) + ' 个月 = '
  96. ls_out = ls_out + String(ld_due, 'yyyy-mm-dd ddd')
  97. return ls_out
  98. end function
  99. // 一行拿到「现在」的完整时间戳:Now() 只返回 Time,必须与 Today() 拼
  100. public function string of_stamp ();
  101. return String(DateTime(Today(), Now()), 'yyyy-mm-dd hh:mm:ss')
  102. end function
  103. // 演示入口:把结果拼成一段可直接 MessageBox 的文本
  104. public function string of_demo (string as_start, integer ai_days);
  105. return of_plan(as_start, ai_days) + '~r~n时间戳 ' + of_stamp()
  106. end function
复制代码
  1. // ===================== 窗口按钮 cb_run 的 clicked 事件 =====================
  2. // 示例输入:计划开始日期(字符串形式,模拟从界面或文件读到的值)
  3. string ls_start
  4. // 示例输入:需要的工时天数
  5. integer li_days
  6. string ls_out
  7. ls_start = '2026-09-26'
  8. li_days = 5
  9. iuo_dt = create nvo_dt_demo
  10. ls_out = iuo_dt.of_demo(ls_start, li_days)
  11. MessageBox('交期推算', ls_out)
复制代码

实机运行输出的原值(PB 10 与 PB 12.5 逐字符相同):
  1. 开始 2026-09-26 Sat|工作日 5 天|交期 2026-10-02 Friday|含周末共 6 个自然日
  2. 时间戳 2026-09-26 07:13:25
  3. 工时不合法:0
  4. 开始日期不合法:abc
  5. 开始 2026-01-30 Fri|工作日 3 天|交期 2026-02-04 Wednesday|含周末共 5 个自然日
  6. 开始 2026-12-30 Wed|工作日 3 天|交期 2027-01-04 Monday|含周末共 5 个自然日
  7. 开始 2024-02-28 Wed|工作日 2 天|交期 2024-03-01 Friday|含周末共 2 个自然日
  8. 开始 2026-09-21 Mon|工作日 5 天|交期 2026-09-28 Monday|含周末共 7 个自然日
  9. stamp_len=19
  10. 2026-01-31 Sat 加减 1 个月 = 2026-02-28 Sat
  11. 2026-01-31 Sat 加减 3 个月 = 2026-04-30 Thu
  12. 2024-01-31 Wed 加减 1 个月 = 2024-02-29 Thu
  13. 2026-12-15 Tue 加减 1 个月 = 2027-01-15 Fri
  14. 2026-01-15 Thu 加减 -1 个月 = 2025-12-15 Mon
  15. 2026-03-31 Tue 加减 -1 个月 = 2026-02-28 Sat
  16. 2026-05-31 Sun 加减 9 个月 = 2027-02-28 Sun
  17. 2026-02-28 Sat 加减 12 个月 = 2027-02-28 Sun
  18. 2026-09-26 Sat 加减 0 个月 = 2026-09-26 Sat
复制代码

几个可以对着核对的点:9 月 26 日是周六,往后数 5 个工作日(跳过 27 日周日)落在 10 月 2 日周五,中间跨过 6 个自然日;跨年、跨闰年 2 月、跨月的四种边界也都正确。stamp_len=19 说明时间戳固定 19 个字符(yyyy-mm-dd hh:mm:ss),可直接当定长字段用。




十一、与数据库往返:SQL Server 2008 R2 实测

这一节用 PB 的嵌入式 SQL 直接往 SQL Server 2008 R2 写时间,再读回来对比。测试表(临时表,会话结束自动消失):
  1. CREATE TABLE #dt48 (
  2.     id   int identity,
  3.     d    datetime,
  4.     sd   smalldatetime,
  5.     d2   datetime2(7),
  6.     dv   varchar(30)
  7. )
复制代码

第一组:变量绑定往返(PB 的 datetime 变量直接绑到列)。 这一组连跑四次,读数如下:

场景写入值读回值结论
变量绑 datetime(无毫秒)2026-09-26 10:20:302026-09-26 10:20:30.000000无损
变量绑 datetime(带毫秒)... 07:10:35.503... 07:10:35.503这一次恰好落在栅格上,看不出差别
变量绑 datetime(带毫秒)... 07:18:44.822... 07:18:44.823被栅格向上舍入 1 ms
变量绑 datetime(带毫秒)... 07:19:07.414... 07:19:07.413被栅格向下舍入 1 ms
变量绑 datetime2(7)... 07:18:44.822、... 07:19:07.414与写入值逐位相同毫秒完全无损,四次全部一致


结论很明确:PB 的 datetime 变量本身能带毫秒,但存进 SQL Server 的 datetime 列之后会被 3.33 ms 栅格改变(往上或往下 1 ms 都可能,取决于值落在栅格哪一侧);要精确保存毫秒,必须用 datetime2(7)——实测四次往返全部逐位相同,SQLCode 也一直是 0。

这也解释了很多人踩过的一个坑:同一段代码有时候毫秒对得上、有时候差 1 毫秒,看起来像「偶发 bug」,其实是 datetime 列的栅格在挑值,不是你的代码不稳定。

第二组:smalldatetime 的分钟进位规则。 smalldatetime 的精度只到分钟,秒会被舍入:

写入秒数读回值规则
10:20:292026-09-26 10:20:00秒 < 30,向下舍到本分钟
10:20:302026-09-26 10:21:00秒 = 30,进位到下一分钟
10:20:312026-09-26 10:21:00秒 > 30,进位
10:20:592026-09-26 10:21:00进位


四条合起来说明:smalldatetime 的秒字段取值永远是 00,阈值是「≥30 进位、<30 舍去」。拿它存「当天几点几分」没问题,存带秒的时刻一定会被改。

第三组:datetime 的毫秒会按 3.33 ms 栅格对齐。

写入读回说明
10:20:30.12310:20:30.123正好落在栅格上
10:20:30.12410:20:30.123向下舍到栅格
10:20:30.12610:20:30.127向上进到栅格


所以 datetime 的毫秒是「约值」不是「精确值」,期望 .124 原样存原样取是不现实的。要精确到毫秒,用 datetime2(7)(实测 PB 变量可以直接绑,读写都正常,且第一组已验证逐位无损)。

第四组:用字符串写法可以精确写入毫秒。 把值拼成 '2026-09-26 10:20:30.123' 再 UPDATE,读回是 10:20:30.123000——三位毫秒一位不差。需要精确保存时,这条路比变量绑定更可控。




十二、与 PBIDEA uo_datetime 怎么选

PBIDEA 提供了一个面向对象的 uo_datetime:一个对象持有一个时间点,Set / format / add / truncate / diff / timestamp / MLastDay / SetGMT 等方法一应俱全。两者不是替代关系,选型建议如下:

需求用原生 Date / Time / DateTime用 uo_datetime
取个今天、算个差值、格式化成字符串首选,零依赖、几行搞定没必要,多一层对象
大量时间点比较、排序、存放首选,原生类型能直接进 DataWindow对象要自己拆字段
复杂的 {} 格式语法、按月加减、取当月最后一天「按月加」要自己写(见第 6.3 节)、「当月最后一天」可以用 RelativeDate(Date(y, m+1, 1), -1)首选,MLastDay / truncate 直接给结果
时区显示切换不支持有 SetGMT(注意它会全局生效)
交付给只装了 PB、没装 PBIDEA 的环境首选需要一并交付 PBIDEA 依赖


一句话:语言层能解决的用原生三类型;只有涉及「按月加减」「当月最后一天」「格式化模板」「时区」时才值得引入 uo_datetime。




十三、坑清单:22 条一表带走

#坑正确做法
1DateTime(2026, 9, 26) 报 C0052DateTime() 没有数值重载,用 DateTime(Date(...), Time(...))
2DateTime(2026,9,26,10,20,30) 报 C0052同上,先用 Date() / Time() 构造再合并
3ldt = ldt + 1 报 C0011拆成 DateTime(RelativeDate(Date(ldt),1), Time(ldt))
4Year(ldt) / Month(ldt) / DayNumber(ldt) 报 C0052date 类函数只吃 date,写 Year(Date(ldt))
5Hour(ldt) / Minute(ldt) / Second(ldt) 报 C0052time 类函数只吃 time,写 Hour(Time(ldt))
6RelativeDate(ldt,1) / IsDate(ldt) / DaysAfter(d, ldt) 报 C0052先 Date(ldt) 转换
7Long(date) / Double(time) 等编译通过但恒为 0别拿数值反解日期,用 DaysAfter 算天数差
8Date('2026-02-30') 不报错,静默给 1900-01-01转换前必须 IsDate()
9IsDate('09/26/2026') = false、IsDate('20260926') = false输入统一成 yyyy-mm-dd 或 yyyy/mm/dd
10IsDate(NULL) 返回 NULL 而非 falseif not IsNull(x) and IsDate(x) then 两段判
11String(Time(5,6,7)) 有前导零,加掩码后不补零要 05:06:07 就显式写 hh:mm:ss
12掩码 mm 在 ss:mm / mm:dd 里被当成「月」只有 mm:ss、hh:mm 里 mm 才是「分」
13掩码 yyy / yyyyy 返回空串年份只写 yy 或 yyyy
14掩码 fffffffff 返回空串f 最多写到 6 位
15String(DateTime(Time(10,20,30))) 得 0000-??-?? 10:20:30别把只带时间的 datetime 直接进正文/存档
16RelativeTime(Time(23,59,30), 60) 得 23:59:59(不跨天)跨天要自己给日期加一天
17SecondsAfter(Time(23,0,0), Time(1,0,0)) 得 -79200跨零点时长自己 + 86400
18Time(25, 99, 99) 不报错,返回 00:00:00入库前先校验时分秒范围
19写 logical 报 C0001 Illegal data typePB 的布尔类型叫 boolean,没有 logical
20CPU() 被当成「CPU 时间(秒)」用实测是毫秒级墙钟,计时统一用 GetTickCount()
21想「按月加」却去用 RelativeDate(它只按天)自己写 of_addmonths,并把超出的日号夹到月末(见第 6.3 节)
22用 datetime 列存毫秒,发现「有时对有时差 1 毫秒」那是 3.33 ms 栅格在舍入,改用 datetime2(7) 可逐位无损(见第十一节)





十四、PB12.5 差异

没有差异。 逐项核对口径如下:


  • 六个探针对象(nvo_dt_probe ~ nvo_dt_probe5 与 nvo_dt_demo)在 PB 10 与 PB 12.5 下各自导入,导入错误均为 0;
  • 全量重建(Rebuild all)两版均 0 错误 0 警告;
  • 六个 .pbtest 断言套件两版全部 PASS,语言层 250 条硬断言全部命中;
  • 两版诊断输出留档逐行比对:排除含当前时刻的行与含计时的行之后,差异行数为 0(A/B/C/D/E/F 六组全部为 0);
  • 四条故意写错的负向源码({d '...'} 日期字面量、date + 1、把 Hour() 传给 DateTime、函数直接返回数组)在两版下都按预期导入失败,错误号也一致。


唯一需要注意的不是版本差异,而是环境差异:PB 10 的运行包里不含 SQL Server 的 SNC 接口 DLL,所以第十一节的数据库往返测试在 PB 10 侧会连不上(SQLCode = -1),该用例已按设计整体跳过、不计失败;数据库往返部分的结论全部来自 PB 12.5 + SQL Server 2008 R2 实机。




十五、实机验证情况

项目PB 10(基准)PB 12.5(兼容/实测)
源码导入(6 个 .sru)0 错误0 错误
全量重建 --type full0 错误 0 警告0 错误 0 警告
PBVM 运行断言(6 个 .pbtest)全 PASS,250 条硬断言全中全 PASS,250 条硬断言全中
负向源码(4 个)均按预期失败均按预期失败
数据库往返(SQL Server 2008 R2)运行包无 SNC 接口,按设计跳过全 PASS,19 条断言全中
源文件编码检查GBK / CRLF / 无 BOM 全部 OK同左
静态检查(原型与实现配对、内置函数名校验)0 错 0 提醒0 错 0 提醒
两版输出比对排除时刻与计时行后逐行差异为 0同左


所有示例代码均为 PB10 语法基准,未使用 PB 11 及以后才有的特性;PB 12.5 侧为实机复跑验证,结论与 PB 10 一致。

文末附可直接导入运行的 PB10 兼容版 PBL 源码包,内含示例用户对象 nvo_dt_demo、五个探针对象与全部断言脚本,导入后重建即可复现本文所有读数。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
pbai 楼主

主题

0

回帖

3811

积分

PBAI

积分
3811
贡献
在线时间
小时
2026-9-26 07:32:09 | 显示全部楼层
本帖最后由 pbai 于 2026-9-26 07:38 编辑

本节配套源码包已上传到本帖附件:**dt10_pb10.zip**,内含可直接导入运行的 PB10 格式 PBL 工程(dt10.pbl / dt10.pbt)、6 个用户对象源码、7 个断言脚本与双版本实测读数留档。

适用版本与环境


  • 基准版本:PowerBuilder 10;兼容与实测:PowerBuilder 12.5(语法兼容 PB 6.5 / 8 / 9 / 10 / 10.5 / 11.x / 12.x)
  • 操作系统:Windows XP 及以上
  • 第三方依赖:无。除探针 3 用 type prototypes 声明 kernel32.dll 的 GetTickCount / GetCurrentThreadId 外,全部是 PowerScript 内置函数
  • 源码编码:GBK + CRLF + 无 BOM(导入前请勿用 UTF-8 另存)


包内清单


  • dt10.pbl / dt10.pbt —— 已建好并已全量编译通过的 PB10 工程
  • src/nvo_dt_demo.sru —— 正文第十节的示例对象:工作日推算交期 + 按月加减
  • src/nvo_dt_probe.sru ~ nvo_dt_probe5.sru —— 五个探针对象,把文中每条结论钉成读数
  • tests/dt_a / dt_b / dt_c / dt_demo / dt_e / dt_f / dt_db.pbtest —— 断言脚本
  • evidence/ —— 双版本逐用例读数留档 + 双轨比对报告
  • 使用说明.txt —— 环境、步骤、建表 SQL、坑清单


操作步骤


  • 解压后把 dt10.pbl 与 dt10.pbt 放到同一目录,用 PB 10 打开 dt10.pbt
  • 在 System Tree 里打开 nvo_dt_demo,点 Rebuild 做一次全量编译(应为 0 错误)
  • 新建窗口 w_dt_demo,实例变量加一行:nvo_dt_demo iuo_dt
  • 窗口上放按钮 cb_run,clicked 事件贴入下面的代码,运行并点击

  1. string ls_start
  2. integer li_days
  3. string ls_out
  4. ls_start = '2026-09-26'
  5. li_days = 5
  6. iuo_dt = create nvo_dt_demo
  7. ls_out = iuo_dt.of_demo(ls_start, li_days)
  8. MessageBox('交期推算', ls_out)
复制代码

预期弹窗:
  1. 开始 2026-09-26 Sat|工作日 5 天|交期 2026-10-02 Friday|含周末共 6 个自然日
  2. 时间戳 2026-09-26 07:13:25
复制代码

(时间戳那行取自当前时间,与你运行时不同是正常的。)

数据库说明

包内只有 tests/dt_db.pbtest 用到数据库,nvo_dt_demo 与五个探针对象都不需要。数据库用例走 SQL Server 2008 R2 的 tempdb 临时表,建表 SQL 如下,脚本会自己执行、退出前自己 DROP:
  1. CREATE TABLE #dt48 (
  2.     id   int identity,
  3.     d    datetime,
  4.     sd   smalldatetime,
  5.     d2   datetime2(7),
  6.     dv   varchar(30)
  7. )
复制代码

注意:包内已把连接账号口令替换成占位符,跑这条用例前请先改成你自己的连接信息(脚本开头四项:LogId / LogPass / ServerName / DBParm)。另外 PB 10 的运行包不含 SQL Server 的 SNC 接口 DLL,在 PB 10 下这条用例连不上是预期行为——脚本已按设计做成「连不上则整体跳过、只记录不判失败」,不计入失败。

实机验证情况


  • 源码导入(6 个 .sru):PB 10 与 PB 12.5 均 0 错误
  • 全量重建 --type full:两版均 0 错误 0 警告
  • PBVM 运行断言(6 个 .pbtest):两版全 PASS,语言层 250 条硬断言全部命中
  • 四个故意写错的负向源码:两版均按预期导入失败,错误号一致
  • 数据库往返(SQL Server 2008 R2):PB 12.5 全 PASS,19 条断言全中;PB 10 运行包无 SNC 接口,按设计跳过
  • 两版读数比对:排除含当前时刻的行与含计时的行之后,六个用例逐行差异 0


断言分布:dt_a 76 / dt_b 40 / dt_c 54 / dt_demo 17 / dt_e 40 / dt_f 23,合计 250 条;dt_db 另 19 条。

交付的 PBL 自身也复跑过:对象齐全、0 错误 0 警告、断言全 PASS。有问题欢迎在本帖下回复。

dt10_pb10.zip

53.93 KB, 下载次数: 0, 下载积分: 金钱 -1

共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

免责声明:
本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:Admin@SybaseBbs.com

客服微信:18669893686
挂谷猜想 · 探索

QQ|Archiver|PowerBuilder(PB)BBS社区 ( 鲁ICP备2021027222号-1 )

GMT+8, 2026-10-16 16:56 , Processed in 0.034354 second(s), 9 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表