0

I would like to split a column into rows using excel office script but I cant figure out how.

I have a schedule in below format in excel which I would like to split into columns.

Original table

Final table need to be like this.

Final Table

Is this achievable, if yes, could someone please share the code

5
  • Sorry but how does it know which dates to pick for the given days between the start and end date? Your example doesn't look complete to me, am I correct? Also, what have you tried yourself? Anything? Commented Nov 26, 2021 at 0:06
  • Hello @Skin, Currently I am using Power Query but was looking to automate with power automate and office script. Sorry but I couldn't find the API to split text in documentation, I tried recording but got error // Undefined event received with MacroRecorderEventId: 207. Power query splits down by delimiter in Days column, then i assign dates from the range based on actual date of that day in the table Commented Nov 26, 2021 at 3:05
  • Please provide enough code so others can better understand or reproduce the problem. Commented Nov 29, 2021 at 15:41
  • @GeorgeChitechi, I'm curious what action or command were you trying to record, was it "Text to columns"? Commented Nov 30, 2021 at 1:52
  • Yes, was trying to record text to columns so I split by delimeter Commented Nov 30, 2021 at 5:08

2 Answers 2

1

Based on your description I made an attempt at a solution with Office Scripts. It takes a table like this:

Table of activities on days of week

and outputs a new table on a new worksheet, like this:

Table of dates with activities

For better or worse I attempted to keep the logic in the workbook via formulas derived from the first table and output into the second. This formula logic would need to be rewritten if there is more than one activity per day.

I'm not a developer but I can already see areas in this Office Script that need improvement:

function main(workbook: ExcelScript.Workbook) {
  // delete new worksheet if it already exists so the rest of the script can run effectively
  // should you need to retain data simply rename worksheet before running
  if (workbook.getWorksheet("My New Sheet") != undefined) {
    workbook.getWorksheet("My New Sheet").delete()
  }
  // assumes your original data is in a table
  let myTable = workbook.getTable("Table1");
  let tableData = myTable.getRangeBetweenHeaderAndTotal().getValues();
  
  // extract the dates as excel serial numbers
  let allDates:number[] = [];
  for (let i = 0; i < tableData.length; i++) {
    allDates.push(tableData[i][2], tableData[i][3]);
  }
  let oldestDate = Math.min(...allDates);
  let newestDate = Math.max(...allDates);
  let calendarSpread = newestDate-oldestDate+2;

  // construct formula from the tableData
  // first add a new 'column' to tableData to represent the days of the week (as numbers) on which the activity is planned. this will be an array added to each 'row'.
  for (let r = 0; r < tableData.length; r++) {
    tableData[r].push(findDay(tableData[r][1]));
  }
  // start a near blank formula string
  let formulaText:string = '=';
  // use the following cell reference
  let cellRef = 'C2';
  // construct the formual for each row in the data and with each day of the week for the row
  let rowCount:number;
  for (let r = 0; r < tableData.length; r++) {
    if (tableData[r][4].length > 1) {
      formulaText += 'IF(AND(OR(';
    } else {
      formulaText += 'IF(AND(';
    }
    for (let a=0; a < tableData[r][4].length; a++) {
      formulaText += 'WEEKDAY(' + cellRef + ')=' + tableData[r][4][a].toString();
      if (a == tableData[r][4].length - 1 && tableData[r][4].length > 1) {
        formulaText += '),';
      } else {
        formulaText += ', ';
      }
    }
    formulaText += cellRef + '>=' + tableData[r][2] + ', ' + cellRef + '<=' + tableData[r][3] + '), "' + tableData[r][0] + '", ';
    rowCount = r+1;
  }
  formulaText += '"-"';
  for (let p=0; p<rowCount; p++) {
    formulaText += ')';
  }

  // create a new sheet
  let newSheet = workbook.addWorksheet("My New Sheet");
  // add the header row
  let header = newSheet.getRange("A1:C1").setValues([["Activity", "Day", "Date"]])
  // insert the oldest date into the first row, then add formula to adjacent cells in row
  let firstDate = newSheet.getRange("C2")
  firstDate.setValue(oldestDate);
  firstDate.setNumberFormatLocal("m/d/yyyy");
  firstDate.getOffsetRange(0, -1).setFormula("=C2");
  firstDate.getOffsetRange(0, -1).setNumberFormatLocal("ddd");
  firstDate.getOffsetRange(0, -2).setFormula(formulaText);
  // use autofill to copy results down until the last day in the sequence
  let autoFillRange = "A2:C" + (calendarSpread).toString();
  firstDate.getResizedRange(0, -2).autoFill(autoFillRange, ExcelScript.AutoFillType.fillDefault);
  // convert the the range to a table and format the columns
  let outputTable = newSheet.addTable(newSheet.getUsedRange(), true);
  outputTable.getRange().getFormat().autofitColumns();
  //navigate to the new sheet
  newSheet.activate();
}

// function to return days (as a number) for each day of week found in a string 
function findDay(foo: string) {
  // start with a list of days to search for
  let daysOfWeek:string[] = ["Sun", "Mon", "Tue", "Wed", "Thur", "Fri", "Sat"];
  //create empty arrays
  let searchResults:number[] = [];
  let daysFound:number[] = [];
  // search for each day of the week, this will create an array for each day of the week where the value is -1 if the day is not found or write the position where the day is found
  for (let d of daysOfWeek) {
    searchResults.push(foo.search(d));
  }
  // now take the search results array and if the number contained is greater than -1 add it's position+1 to a days found array. this should end up being a list of numbered days of the week found in a string/cell
  for (let i = 0; i < searchResults.length; i++) {
    if (searchResults[i] > -1) {
      daysFound.push(i + 1);
    }
  }
  return daysFound
}
Sign up to request clarification or add additional context in comments.

2 Comments

Very nice, at some point I thought it was not possible to achieve this, Thanks alot. Its complaining about data types but working. Will test with several Activities, dates and days and revert. Thanks again. below some of the complaints [11, 25] The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. [11, 32] The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. [13, 16] The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
Yes there are several typing problems which I'm still learning how to correct.
0

I managed to get this working using below code for anyone who might be interested.

  workbook.getWorksheet('UpdatedSheet')?.delete()
  let usedRange = workbook.getActiveWorksheet().getTables()[0].getRangeBetweenHeaderAndTotal();
  let newString: string[][] = [];

  usedRange.getValues().forEach(row => {
    let daysRows: string[][] = [];
    let days = row[1].toString().split(',');
    days.forEach(cellValue => {
      if (cellValue != ' ') {
        let eachDayData = row.toString().replace(row[1].toString(), cellValue).split(',');
        daysRows.push(eachDayData);
      }
    });
    daysRows.forEach(actualDay => {
      const effDate = new Date(Math.round((actualDay[2] as unknown as number - 25569) * 86400 * 1000))
      const disDate = new Date(Math.round((actualDay[3] as unknown as number - 25569) * 86400 * 1000))

      getDatesInRange(effDate, disDate).forEach(element => {
        let options = { weekday: 'short' }
        if (element.toLocaleDateString('en-GB', options) == actualDay[1]) {
          let datas = actualDay.toString().replace(actualDay[2], element.toDateString()).split(',')
          datas.pop()
          newString.push(datas)
        }
      });
    });
  });
  workbook.getWorksheet('UpdatedSheet')?.delete()
  let workSheet = workbook.addWorksheet('UpdatedSheet');
  workSheet.activate();
  let headers = workSheet.getRange('A1:C1').setValue([['Activity', 'Day', 'Date']])
  let range = workSheet.getRange('A2');
  let resizedRange = range.getAbsoluteResizedRange(newString.length, newString[0].length);
  resizedRange.setValues(newString);

  let tableRange = workSheet.getRange("A2").getSurroundingRegion().getAddress();
  let newTable = workbook.addTable(workSheet.getRange(tableRange), true);
  newTable.setName('updatedTable');
  workSheet.getRange().getFormat().autofitColumns()
}

function getDatesInRange(startDate: { getTime: () => string | number | Date; }, endDate: string | number | Date) {
  const date = new Date(startDate.getTime());
  const dates: Date[] = [];

  while (date <= endDate) {
    dates.push(new Date(date));
    date.setDate(date.getDate() + 1);
  }
  return dates;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.