根据包含日期和时间的两列对范围或数组进行排序

时间:2018-04-16 15:14:03

标签: sorting google-apps-script google-sheets

目前,我正在尝试为Google表格创建Google Apps脚本,这样可以为即将发生的事件分批添加每周定期事件。然后,我的同事会对这些添加的事件进行微小的更改(例如,更改日期和时间,更改联系人,添加事件所需的材料等)。

到目前为止,我编写了以下脚本:

function CopyWeeklyEventRows() {
  var ss = SpreadsheetApp.getActiveSheet();
  var repeatingWeeks = ss.getRange(5,1).getValue(); // gets how many weeks it should repeat
  var startDate = ss.getRange(6, 1).getValue(); // gets the start date
  var startWeekday = startDate.getDay(); // gives the weekday of the start date
  var regWeek = ss.getRange(9, 2, 4, 7).getValues(); // gets the regular week data
  var regWeekdays = new Array(regWeek.length); // creates an array to store the weekdays of the regWeek
  var ArrayStartDate = new Array(startDate); // helps to store the We

  for (var i = 0; i < regWeek.length; i++){ // calculates the difference between startWeekday and each regWeekdays
    regWeekdays[i] = regWeek[i][1].getDay() - startWeekday;
    Logger.log(regWeekdays[i]);
    // Add 7 to move to the next week and avoid negative values
    if (regWeekdays[i] < 0) {
      regWeekdays[i] = regWeekdays[i] + 7;
    }
    // Add days according to difference between startWeekday and each regWeekdays
    regWeek[i][0] = new Date(ArrayStartDate[0].getTime() + regWeekdays[i]*3600000*24);
  }
  // I'm struggling with this line. The array regWeek is not sorted:
  //regWeek.sort([{ column: 1, ascending: true }]);
  ss.getRange(ss.getLastRow() + 1, 2, 4, 7).setValues(regWeek); // copies weekly events after the last row
}

它允许根据开始日期将一周的周期性事件添加到电子表格的概述部分。如果开始日期是星期二,则从星期二开始添加常规星期。 However, the rows are not sorted according to the datesdates are not sorted

在将行添加到概述之前,如何按升序日期(后跟时间)对行进行排序?

我对类似问题的搜索显示Google Script sort 2D Array by any column这是我发现的最接近的打击。使用sort行运行脚本时会显示相同的错误消息。我不明白Rangearray之间的差异,这可能有助于解决问题。

为了让您了解更广泛的情况,请参阅我目前正在进行的工作:

  • 我注意到添加时格式不一定会保留 新的经常性事件。到目前为止,我还没有找到规则并按格式化 第二步。
  • 目前的一个缺点是每周定期活动部分 固定。我试图找到最后填写的条目并使用它来设置 范围regWeek,但卡住了。
  • 使用A列排除添加的重复事件 使用下拉列表进行处理。
  • 允许我的同事使用a将事件添加到周期性事件中 下拉(例如A26)。然后应该添加此事件并进行排序 一周中的正确日期和开始时间。排序将进入 方便。

提前感谢您对排序的意见以及如何改进代码的建议。

A demo version of the spreadsheet

UpdateV01:

这里是复制和排序的代码行(首先按日期,然后按时间)

ss.getRange(ss.getLastRow()+1,2,4,7).setValues(regWeek); // copies weekly events after the last row
ss.getRange(ss.getLastRow()-3,2,4,7).sort([{column: 2, ascending: true}, {column: 4, ascending: true}]); // sorts only the copied weekly events chronologically

正如@tehhowch指出的那样,这很慢。最好在写作之前进行排序。 我将实现此方法并在此处发布。

UpdateV02:

regWeek.sort(function (r1, r2) {
// sorts ascending on the third column, which is index 2
return r1[2] - r2[2];
});
regWeek.sort(function (r1, r2) {
// r1 and r2 are elements in the regWeek array, i.e.
// they are each a row array if regWeek is an array of arrays:
// Sort ascending on the first column, which is index 0:
// if r1[0] = 1, r2[0] = 2, then 1 - 2 is -1, so r1 sorts before r2
return r1[0] - r2[0];
});

UpdateV03:

这是尝试在几周内重复重复发生的事件。不知道如何将整个&#34;周&#34;包括在内。

// Repeat week for "A5" times and add to start/end date
for (var j = 0; j < repeatingWeeks; j++){
  for (var i = 0; i < numFilledRows; i++){
  regWeekRepeated[i+j*6][0] = new Date(regWeek[i][0].getTime() + j*7*3600000*24); // <-This line leads to an error message
  regWeekRepeated[i+j*6][3] = new Date(regWeek[i][3].getTime() + j*7*3600000*24);
  }
}

我的问题得到了解答,我能够使代码按预期工作。

2 个答案:

答案 0 :(得分:0)

您可以在设置regWeek变量之前对“Weekly Events”范围进行排序。然后,在处理之前,范围将按您想要的顺序排列。或者您可以在设置数据后对整个“概述”范围进行排序。这是一个快速功能,您可以调用以按多列对范围进行排序。您当然可以调整它以对“每周事件”范围进行排序,而不是“概述”范围。

function sortRng() {
  var ss = SpreadsheetApp.getActiveSheet();
  var firstRow = 22; var firstCol = 1;
  var numRows = ss.getLastRow() - firstRow + 1; 
  var numCols = ss.getLastColumn();
  var overviewRng = ss.getRange(firstRow, firstCol, numRows, numCols);
  Logger.log(overviewRng.getA1Notation());
  overviewRng.sort([{column: 2, ascending: true}, {column: 4, ascending: true}]);
}

至于获取每周事件部分中已填充行的数量,您需要搜索一个总是有数据的列,如果任何行有数据(如开始日期列b),循环遍历值和第一次它找到一个空白,返回该数字。这将为您提供需要复制的行数。警告:如果“每周事件”部分和“概述”部分之间的B列中没有至少一个空白值,则可能会得到不需要的结果。

function getNumFilledRows() {
  var ss = SpreadsheetApp.getActiveSheet();
  var eventFirstRow = 9; var numFilledRows = 0;
  var colToCheck = 'B';//the StartDate col which should always have data if the row is filled
  var vals = ss.getRange(colToCheck + eventFirstRow + ":" + colToCheck).getValues();
  for (i = 0; i < vals.length; i++) {
    if (vals[i][0] == '') {
      numFilledRows = i;
      break;
    }
  }
  Logger.log(numFilledRows);
  return numFilledRows;
}

编辑: 如果您只想在写入之前在javascript中对数组进行排序,并且您希望首先按开始日期排序,那么按时间排序,您可以创建一个临时数组,并为每个日期和时间组合的行添加一列。 array.sort()按字母顺序排序日期,因此您需要将该日期转换为整数。然后,您可以按新列对数组进行排序,然后从每行中删除新列。我在下面添加了一个函数。它可能更紧凑,但我认为它可能更清晰。

function sortDates() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var vals = ss.getActiveSheet().getRange('B22:H34').getDisplayValues(); //get display values because getValues returns time as weird date 1899 and wrong time.
  var theDate = new Date(); var newArray = []; var theHour = ''; var theMinutes = '';
  var theTime = '';
  //Create a new array that inserts date and time as the first column in each row
  vals.forEach(function(aRow) {
    theTime = aRow[2];//hardcoded - assumes time is the third column that you grabbed
    //get the hours (before colon) as a number
    theHour = Number(theTime.substring(0,theTime.indexOf(':'))); 
    //get the minutes(after colon) as a number
    theMinutes = Number(theTime.substring(theTime.indexOf(':')+1)); 
    theDate = new Date(aRow[0]);//hardcoded - assumes date is the first column you grabbed.
    theDate.setHours(theHour);
    theDate.setMinutes(theMinutes);
    aRow.unshift(theDate.getTime()); //Add the date and time as integer to the first item in the aRow array for sorting purposes.
    newArray.push(aRow);
  });
  //Sort the newArray based on the first item of each row (date and time as number)
  newArray.sort((function(index){
    return function(a, b){
        return (a[index] === b[index] ? 0 : (a[index] < b[index] ? -1 : 1));
    };})(0));
  //Remove the first column of each row (date and time combined) that we added in the first step
  newArray.forEach(function(aRow) {
    aRow.shift();
  });
  Logger.log(newArray); 
} 

答案 1 :(得分:0)

鉴于您的评论 - 您想要对写入的块进行排序 - 您有两种方法可用。一种是使用电子表格服务的Range#sort(sortObject)方法在编写之后对书面数据进行排序。另一种是使用JavaScript Array#sort(sortFunction())方法在写入之前对数据进行排序。

目前,您的排序代码//regWeek.sort([{ column: 1, ascending: true }]);正在尝试使用Spreadsheet服务所需的排序对象对JavaScript数组进行排序。因此,您可以简单地将此.sort(...)调用链接到您的写入调用,因为Range#setValues()返回相同的Range,允许重复Range方法调用(例如设置值,然后应用格式等。)。

这看起来像:

ss.getRange(ss.getLastRow() + 1, 2, regWeek.length, regWeek[0].length)
   .setValues(regWeek)
   /* other "chainable" Range methods you want to apply to
       the cells you just wrote to. */
   .sort([{column: 1, ascending: true}, ...]);

在这里,我更新了您访问的范围,以引用您尝试编写的数据 - regWeek - 以便始终保持数据的正确大小。我也在视觉上拆开了单行,这样你就可以更好地看到&#34;链接&#34;这是在电子表格服务电话之间发生的。

另一种方法 - 在写入之前进行排序 - 会更快,特别是随着排序的大小和复杂性的增加。排序范围背后的想法是你需要使用一个函数,当第一个索引的值应该在第二个索引之前时返回一个负值,当第一个索引的值应该是正值时在第二个之后,如果它们是等价的,则为零值。这意味着返回boolean的函数不会按照人们的想法排序,因为false0在Javascript中是等效的,而true1也是等价的。

您的排序如下所示,假设regWeek是一个数组数组,并且您正在对数值进行排序(或者至少会转换为数字,例如Date s)。

regWeek.sort(function (r1, r2) {
  // r1 and r2 are elements in the regWeek array, i.e.
  // they are each a row array if regWeek is an array of arrays:
  // Sort ascending on the first column, which is index 0:
  // if r1[0] = 1, r2[0] = 2, then 1 - 2 is -1, so r1 sorts before r2
  return r1[0] - r2[0];
});

我强烈建议您查看Array#sort documentation