一种计费时长算法

1,问题的提出

停车场管理中,有些月卡是按时段免费停车的,也就是在指定的时间区间内停车,免收停车费;而在此区间以外的时间,则累计为计费时长,需要缴费。

该问题的输入是车辆进出时间T1、T2、免费时段起止时间点sh,eh

输出是计费时长,分钟为单位。

2,问题分析

咋看该问题并不复杂,采用时间点比较法,无非是在T1、T2之间找到免费区间,然后予以扣除即可。

但事实上,需要考虑的因素有:

免费时段起止时间点sh,eh,可能在一天内,也可能分布在2天内,也就是要考虑跨天的因素。

T1到T2的时间跨度X,与sh、eh构造的时间段Y之间的关系较为复杂。

假如考虑sh、eh跨天,那么2天之内,免费的区段就有两个,记为Y1、Y2,其中 Y2在Y1后面

X和Y1、Y2的关系就有如下可能性:

  • X在Y1左侧
  • X在Y2右侧
  • X包含Y1和Y2
  • X与Y1交叉但不包含Y1
  • 。。。

粗略估计,位置关系有10种以上

若再考虑比较时,跨天的时段还得以0点为界,分解为两段,那么算法将非常复杂,极易因考虑不周出错。

3,新的算法思路

由于采用时间点(一天之内的时刻数)比对统计非常复杂,新的思路是采用日期排序比对法。

把所有时间点换算成日期,然后比对,在免费时段就不统计,在非免费时段就加入统计。此法简单明了。

以下是详细步骤:

  1. 取出T1、T2时间对应的日期D1/D2
  2. (技巧)将D1减去一天,设置为计费起始日的前一天。目的是,判断计费起始时间是否处于免费时段。
  3. 将D1和D2之间的日期,连续插入,形成日期列表DL:【D1、C11、C12、…、D2】
  4. 将DL与sh和eh组合,形成两个时间列表
    • TLs:【D1s、C11s、C12s、…、D2s】
    • TLe:【D1e、C11e、C12e、…、D2e】

  5. 将T1/T2/TLs/TLe,组合为一个列表CL:【t1,t2,…,tn】

    • l  按时间排序
    • l  TLs的元素,附加s标记
    • l  TLe的元素,附加e标记

 

  6. 初始化参数:

    • 计费时长Tc=0,起始时点Ts=0,计费标记Tf=0

   7. 对CL中的元素,顺序执行以下算法:

    • 取出元素,ti
    • 若ti>T1,则Tc +=(ti-Ts)*Tf,累计收费时间
    • 若ti=T2,则 退出。
    • 若ti=T1,则 Ts=ti
    • 若ti的标记为s,则 Ts=ti,Tf=0,进入免费区间
    • 若ti的标记为e,则 Ts=ti,Tf=1,进入收费区间

4,参考代码

以下delphi代码,实现了上述思路,算法简明,不易出错,对于跨天、跨月、跨年等均不存在问题,经测试计算结果OK。

function TForm1.CalcMCFeeMinutesV2(sh, eh:integer; sDate, eDate: PChar):integer;
var
  d1, d2: TDatetime;
  Tc: double;
  Ts: TDatetime;
  Tf: integer;
  i: integer;

  Date1, Date2, curTime: TDatetime;
  DL, TL: TStringList;
  s_sh, s_eh: string;
begin
  memo1.Lines.Clear ;
  memo1.Lines.Add('出入时间:' + sDate + '--' + eDate);

  d1 := StrTodateTime(sDate);
  d2 := StrTodateTime(eDate);

  //月卡计费无免费时段,直接返回所有分钟数
  if sh=eh then begin
    Result := round((d2 -d1)*24*60);
    Exit;
  end;

  Date1 := StrToDatetime(formatDatetime('YYYY-MM-DD', d1));  //取日期整数
  Date2 := StrToDatetime(formatDatetime('YYYY-MM-DD', d2));  //取日期整数

  //格式化时间,以便后面排序
  s_sh := IntToStr(sh);
  if length(s_sh)=1 then
    s_sh := '0'+ s_sh;

  s_eh := IntToStr(eh);
  if length(s_eh)=1 then
    s_eh := '0'+ s_eh;

  DL := TStringList.Create;
  TL := TStringList.Create;

  //要多出一天来,以便判断起始时间处于收费时段还是免费时段
  Date1 := Date1 -2;

  for i:=0 to (trunc (d2 -d1)+1) do begin
    Date1 := Date1 + 1;
    if Date1>date2 then break;

    DL.Add(formatDatetime('YYYY-MM-DD', Date1));
  end;

  for i:=0 to DL.Count-1 do begin
    TL.AddObject(DL[i]+' '+ s_sh +':00' , Pointer(1));
    TL.AddObject(DL[i]+' '+ s_eh +':00' , Pointer(2));
  end;

  TL.AddObject(formatDatetime('YYYY-MM-DD HH:MM', d1) , Pointer(3));
  TL.AddObject(formatDatetime('YYYY-MM-DD HH:MM', d2) , Pointer(4));

  TL.Sort;

  for i:=0 to TL.Count-1 do begin
    memo1.Lines.Add(TL[i] +'/'+ IntToStr(Integer(TL.Objects[i])));
  end;
  //初始化参数:计费时长Tc=0,起始时点Ts=0,计费标记Tf=0
  Tc := 0;
  Ts := StrToDatetime(TL[0]);
  Tf := 1;

  for i:=0 to TL.Count-1 do begin
    curTime := StrToDatetime(TL[i]);

    if Integer(TL.Objects[i])=4 then begin
      Tc := Tc + (curTime- Ts)* Tf;
      memo1.Lines.Add(TL[i]+ format('/累计:%f',[Tc]));

      Result := Round(Tc*24*60);
      WriteMemoToLogFile('', memo1.Text  );
      Exit;
    end;

    if Integer(TL.Objects[i])=3 then begin
      //if curTime>d1 then Tc := Tc + (curTime- Ts)* Tf;
      Ts := curTime;
    end;

    if Integer(TL.Objects[i])=1 then begin
      if curTime>d1 then Tc := Tc + (curTime- Ts)* Tf;
      Ts := curTime;
      Tf := 0;
    end;

    if Integer(TL.Objects[i])=2 then begin
      if curTime>d1 then Tc := Tc + (curTime- Ts)* Tf;
      Ts := curTime;
      Tf := 1;
    end;

    memo1.Lines.Add(TL[i]+ format('/累计:%f',[Tc]));
  end;


end;

 

posted @ 2021-11-01 17:18  jack0424  阅读(362)  评论(0编辑  收藏  举报