如何将字符串类型的工作日数据项(日期、工作时间)列表减少为每个工作日范围字符串的列表,每个字符串都有总工作时间?

Posted

技术标签:

【中文标题】如何将字符串类型的工作日数据项(日期、工作时间)列表减少为每个工作日范围字符串的列表,每个字符串都有总工作时间?【英文标题】:How does one reduce a list of string-type workday data-items (date, working hours) into a list of workday-range strings each with total working hours? 【发布时间】:2021-12-30 08:15:24 【问题描述】:

我有一个日期数组,我试图将它们连接起来以在 javascript 中创建一个日期范围。

[更新]:到目前为止,我一直在尝试将每个月/日/年/小时拆分为自己的数组,执行获取日期范围的功能,(尝试)拼接月份以便它们排列加上更新的串联日期范围,然后最后需要添加串联日期的所有小时数。

所以起始数组:

["Nov 23 2021 8 hrs", "Nov 24 2021 8 hrs", "Nov 27 2021 8 hrs", "Dec 3 2021 8 hrs"]

应该是:

["Nov 23-24 2021 16 hrs", "Nov 27 2021 8 hrs", "Dec 3 2021 8 hrs"]

我能够成功拆分数组索引,将日期数字推送到一个新数组中,将它们连接起来,但是我无法弄清楚如何根据第一个日期之后的任何日期摆脱额外的月份索引。

这里是日期连接的代码:

const getDayRanges = (array) => 
  var ranges = [],
    rstart,
    rend;
  for (var i = 0; i < array.length; i++) 
    rstart = array[i];
    rend = rstart;
    while (array[i + 1] - array[i] == 1) 
      rend = array[i + 1]; // increment the index if the numbers sequential
      i++;
    
    ranges.push(rstart == rend ? rstart + "" : rstart + " to " + rend);
  
  return ranges;
;

我猜我需要传入整个数组并让它专门在嵌套的 for 循环中查找 array[i],或者可能为该函数添加一个新参数几个月/几年等进行比较索引?不过,我在具体如何做到这一点上有点卡住。

【问题讨论】:

我不完全理解您希望按照什么标准“合并”输入数组中的日期。是按年/月还是其他?你能详细说明一下吗?您可以更新您的问题以反映这一点) 嘿,谢谢你的回复,我更新了我上面的信息,希望能澄清我想要做什么。 【参考方案1】:

我认为用小时来记录日期的逻辑是有缺陷的。如果您将小时分开保存,例如在 Map 或二维数组中,则使用 Date.parse 和 new Date 可以更容易地实现您想要的内容,您可以检查日期。像这样的:

let date = new Date(Date.parse('Nov 23 2021')) 
let date1 = new Date(Date.parse('Nov 24 2021')) 
//and use Date methods to compare them

否则,您必须创建一个算法来匹配正则表达式,以便找到日期和时间,保留它们,比较它们,组合它们,并将它们添加到一个新列表中。

【讨论】:

OP 试图实现的目标有点复杂。 @peterseliger 我没有提供完整的答案。是的,这就是为什么我说它使它更容易。遍历数组,找到完美无间隙的日期,添加它们的小时数,将它们作为一个包含日期范围和添加的小时数的列表放入列表中,如果数组不按顺序排列,则更难。 是的,我一直在做的是根据索引拆分数组并将它们推送到新数组中。所以我可以按照你的建议把日期分成他们自己的数组。我想这仍然会让我不确定如何根据连接的日期范围将这些日期范围的所有时间加起来。 我现在无法为您提供完美的答案,但是如果您将它们拆分为 Map 假设,您必须找到日期中的序列,然后将第一个和最后一个日期放入发现的序列如 - ['Nov 24 2021 - Nov 27 2021'] => 45 小时。并添加他们的时间。有时最好的解决方案是重建你的逻辑。恕我直言。但如果你真的想使用这种格式,你别无选择,只能使用非常复杂且冗长的算法。【参考方案2】:

这是一个经典的map/reduce 任务。

必须采取以下步骤...

    map 将原始输入数据转换为方便的工作格式,并按日期(和小时)对每个项目进行排序。

    映射后,reduce 数据项通过尝试查找连续(或相同)天的范围。

    在初始 map/reduce 任务(步骤 1+2)之后,再次map;这次将缩减的范围项数据转换为特殊的字符串化范围表示或原始输入格式。

另外...

一个人不仅要处理一个月内的正常日期范围,还要处理跨越数月甚至数年的范围。

必须想出一个正则表达式,它可以提取原始字符串的datahours 值...

/(?&lt;date&gt;[A-Za-z]+\s+\d1,2\s+\d4)\s+(?&lt;hours&gt;\d+(?:\.\d+)?)\s+hrs/

function createWorkDayItem(record) 
  const regXWorkDayData
    = /(?<date>[A-Za-z]+\s+\d1,2\s+\d4)\s+(?<hours>\d+(?:\.\d+)?)\s+hrs/;
  const 
    date = null,
    hours = null,
   = regXWorkDayData.exec(record)?.groups ?? ;

  return 
    record,
    hours: (hours !== null) ? Number(hours) : hours,
    date,
    time: (date !== null) ? new Date(date).getTime() : date,
  ;

function compareWorkDayItems(a, b) 
  return a.time - b.time || b.hours - a.hours;


function collectItemOfSameOrConsecutiveDay(listOfRanges, currentItem, idx, arr) 
  if (idx >= 1) 
    const recentRange = listOfRanges.at(-1);
    const recentRangeItem = recentRange.at(-1);

    const deltaT = Number(
      ((currentItem.time - recentRangeItem.time) / 1000 /60 / 60 / 24) // msec => day(s)
        .toFixed(4)
    );
    if (deltaT <= 1/* day*/) 

      recentRange.push(currentItem)
     else 
      listOfRanges.push([currentItem]);
    
   else 
    listOfRanges.push([currentItem]);
  
  return listOfRanges;


function createWorkDayRangeFormat(rangeItem) 
  let result;
  if (rangeItem.length === 1) 

    result = rangeItem[0].record;
   else 
    const totalHours = rangeItem
      .reduce((total,  hours ) => total + hours, 0);

    const [monthStart, dayStart, yearStart]
      = rangeItem[0].date.split(/\s+/);

    const [monthEnd, dayEnd, yearEnd]
      = rangeItem.at(-1).date.split(/\s+/);

    if (yearStart === yearEnd) 
      if (monthStart === monthEnd) 
        result = [

          monthStart,
          [dayStart, dayEnd].join('-'),
          yearStart,

        ].join(' ');
       else 
        result = [[

          [monthStart, dayStart].join(' '),
          [monthEnd, dayEnd].join(' '),

        ].join(' - '), yearEnd].join(' ');
      
     else 
      result = [

        [monthStart, dayStart, yearStart].join(' '),
        [monthEnd, dayEnd, yearEnd].join(' '),

      ].join(' - ')
    
    result = [result, totalHours, 'hrs'].join(' ');
  
  return result;


const workDayHourList = [
  "Nov 23 2021 8 hrs", 
  "Nov 24 2021 4.5 hrs", 
  "Nov 24 2021 2.5 hrs",

  "Nov 27 2021 8 hrs",
  "Dec 3 2021 8 hrs",
  
  "Dec 16 2021 2.25 hrs",
  "Dec 18 2021 7.75 hrs",
  "Dec 17 2021 6.5 hrs",

  "Dec 1 2021 4 hrs",
  "Nov 30 2021 6 hrs",

  "Dec 31 2020 3.5 hrs",
  "Dec 30 2020 4.25 hrs",
  "Jan 02 2021 3.75 hrs",
  "Jan 01 2021 2.25 hrs",
];

console.log(
  '1st ... map the raw input data and sort it by date and hours ...',
  workDayHourList
    .map(createWorkDayItem)
    .sort(compareWorkDayItems)
);
console.log(
  '2nd ...after mapping, reduce the data by trying to find ranges of consecutive days ...',
  workDayHourList
    .map(createWorkDayItem)
    .sort(compareWorkDayItems)
    .reduce(collectItemOfSameOrConsecutiveDay, [])
);
console.log(
  '3rd ... after initial map/reduce, map again, the reduced (range) date into either a special range or into the original input format ...',
  workDayHourList
    .map(createWorkDayItem)
    .sort(compareWorkDayItems)
    .reduce(collectItemOfSameOrConsecutiveDay, [])
    .map(createWorkDayRangeFormat)
);
.as-console-wrapper  min-height: 100%!important; top: 0; 

【讨论】:

这看起来很有希望,感谢您将其编码出来,我会尽快尝试一下,如果有效,请回复评论。 "...看起来很有希望" ...你在开玩笑。 ... “...如果它有效。” ...有一个测试确实涵盖了 OP 甚至还没有考虑到的边缘情况。 哈哈我不是故意暗示它不起作用。我没有深入研究您实施的所有内容,所以我无法谈论所有这些是否适用于我需要它做的事情,但我看到了您的最终结果,它计算出的所有内容都完全符合我的要求。我还注意到你考虑到这一年是否会延续到下一年,这是完美的。再次感谢您编写代码。 @Logikev ...感谢您的澄清。您当然不客气,并花时间深入研究代码/方法。如果还有任何问题,请不要犹豫。 按预期完美运行,再次感谢!

以上是关于如何将字符串类型的工作日数据项(日期、工作时间)列表减少为每个工作日范围字符串的列表,每个字符串都有总工作时间?的主要内容,如果未能解决你的问题,请参考以下文章

如何将工作日中的整数列添加/减去日期时间列?

如何参考列在工作表之间传输数据?

日期 - 年 - 月模式工作台中的计算成员

如何将所有日期格式转换为日期列的时间戳?

VBA 如何将一列字符转成日期

使用 PySpark 将字符串处理为 RDS 中的日期数据类型列