-1

I vave a column that i only write numbers in the cells. Like for example: I write 15022019 then i go to number formats and choose date. So the number is converted to 15/02/2019. But i don't need everytime when i write a number make the change to date format. I need it automatically. So i found this script:

var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0]; 
var column = sheet.getRange("D3:D31"); 
column.setNumberFormat("dd/mm/yyy");

It work. But is changing the numbers to date format incorrectly. If i write 14022019 its convert to 24/12/40290, and not in 14/02/2019(how i expected). Why that? Just in manually way it converts rightly. My location is Brazil.

Can someone say me what i'm doing wrong?


Edit 1:

I need it to convert to date automatically each time i fill a cell with the date. My range date will be always D3:D31. I tried modify the lines bellow:

function convertnumbertodate(crange){

  // establish spreadsheet credentials
  var ss1=SpreadsheetApp.getActive();
  var sh1=ss1.getActiveSheet();

  // get the range so that rows and columns can be calculated
  var rg1=sh1.getRange(crange);

And in the place of (crange) i put D3:D31 to try to make the conversion to date automatically. Look bellow:

function convertnumbertodate(crange){

  // establish spreadsheet credentials
  var ss1=SpreadsheetApp.getActive();
  var sh1=ss1.getActiveSheet();

  // get the range so that rows and columns can be calculated
  var rg1=sh1.getRange(D3:D31);

But when i run the function convertnumbertodate it reports error. Can you help me how make it convert to date automatically?

Thank you


Edit 2:

Just made what you did:

    function convertnumbertodate() {

      // establish spreadsheet credentials
      var editedCell;
      var sh1=ss1.getActiveSheet();

      // get the range so that rows and columns can be calculated
      var rg1=sh1.getRange(D3:D31);

      // get number of columns
      var numColumns = rg1.getNumColumns();

      // if more than one column chosen, stop the process. 
      if (numColumns !=1){
        //Logger.log("DEBUG: Number of columns in range = "+numColumns); // DEBUG
        var message = "Too Many Columns; one column only";
        return message;
      }
etc.

I deleted the crange and put my range D3:D31 Also made it run OnEdit: var editedCell;

But when i run, it says thats have an error in the line var rg1=sh1.getRange(D3:D31);

Black Mamba
  • 247
  • 1
  • 12
  • Just make the function convertnumbertodate() - remove the “crange” parameter. Then, consider making this an OnEdit function so that it executes as you enter the data. To make sure it doesn’t affect every edit, include a test to check that the edited cell is in the nominated range. There are plenty of precedent topics on StackOverflow. – Tedinoz Feb 20 '19 at 20:09
  • Can you please check my edit 2? – Black Mamba Feb 20 '19 at 21:29
  • Sorry. My mind wan't on the job. Refer "Addendum". This is strictly standalone, onEdit(e). Works one cell at a time. BTW, if any one of these answers has proved helpful, you might consider accepting the answer. – Tedinoz Feb 21 '19 at 03:22

1 Answers1

1

Problem
The OP enters 14022019 in an unformated cell. When the cell is formatted as a date, the value returned is 24 December 40290; the OP expected the date to be 14 February 2019.

Solution
- 1: format the cell as a date before data entry.
- 2: enter number with separators, such as 14/02/2019 or 14-02-2019

Explanation
When the OP types "14022019" into an unformatted cell, they intend that the input should be treated as a date (14 February 2019). However Google treats the contents at face value; there is no inference about date/time. So, when the cell is subsequently formatted as date, the raw value is converted to a date and the cell displays 24 December 40290.

The reason is that the Google Time Epoch began on 31 December 1899 00:00:00 (as opposed to the Unix Time Epoch, which is used by Javascript, which began on January 1, 1970 00:00:00). Secondly, Google measures date-time in days (as opposed to the Unix Epoch that measures elapsed seconds).

This is (roughly) how Google converts 14,022,019 to 24 December 40290.

  • 14,022,019 "days", at a rough average of 365.245 days per year = approximately 38390.7 years.
  • Add on 1899 for the Google Epoch. Running total = 40289.7 years. (roughly mid September 40290)
  • Allow for adjustments for leap years 101.795 days = 0.3 (101.795/365.245); running total = 40290 years. (roughly 24 December 40290)

Note#1: there is a further complication.
The way that Sheets and Apps Script handle "dates" are very different.

  • Sheets: the "date" unit is 1 day; The base date is 1899-12-30 0:00:00, getting the timezone from the spreadsheet settings.
  • Apps Script (being based on JavaScript): the "date" unit is 1 millisecond. The base date is 1970-1-1 00:00:00 UTC.

Reference/Credit: Rubén

Note#2: My reference for the Google Epoch is (https://webapps.stackexchange.com/a/108119/196152)

Note#3: Broadly date/time conversions are based on 3,600 seconds per hour, 86,400 seconds per day, 31,556,926 second per year and 365.24 days per year.


UPDATE - 20 Feb 2019
The OP asks, quite rightly, "so how do I convert the existing cells?"

The code to make the conversion is straightforward: - convert the number to a string - slice the string into components for Day, Month and Year - use the components to create a new date - update the cell with the date

The range to be converted is an potential issue. What is the range, is the range always the the same size, etc? The following code enables an interface for the user to choose a range. The range can then be converted. Arguably this element wasn't essential, but does provide a more flexible, if not elegant, solution.


Code.gs

function onOpen(){
  SpreadsheetApp.getUi()
  .createMenu("Date Convert")
  .addItem("Convert", "selRange")
  .addToUi();
}


function selRange()//run this to get everything started.  A dialog will be displayed that instructs you to select a range.
{
  var output=HtmlService.createHtmlOutputFromFile('pickRange').setWidth(300).setHeight(200).setTitle('Convert to dates');
  SpreadsheetApp.getUi().showModelessDialog(output, 'Convert Numbers to Dates');
}

function selCurRng() 
{
  var sso=SpreadsheetApp.getActive();
  var sh0=sso.getActiveSheet();
  var rg0=sh0.getActiveRange();
  var rng0A1=rg0.getA1Notation();
  rg0.setBackground('#FFC300');
  return rng0A1;

}

function clrRange(range)
{
  var sso=SpreadsheetApp.getActive();
  var sh0=sso.getActiveSheet();
  var rg0=sh0.getRange(range);
  rg0.setBackground('#ffffff');
}

function convertnumbertodate(crange){

  // establish spreadsheet credentials
  var ss1=SpreadsheetApp.getActive();
  var sh1=ss1.getActiveSheet();

  // get the range so that rows and columns can be calculated
  var rg1=sh1.getRange(crange);

  // get number of columns
  var numColumns = rg1.getNumColumns();

  // if more than one column chosen, stop the process. 
  if (numColumns !=1){
    //Logger.log("DEBUG: Number of columns in range = "+numColumns); // DEBUG
    var message = "Too Many Columns; one column only";
    return message;
  }

  // get the first row and the number of rows
  var rowqty = 1;
  var rownum = rg1.getRow();
  // Logger.log("DEBUG: first row = "+rownum);//DEBUG
  var rowqty = rg1.getNumRows();
  // Logger.log("DEBUG: Number of rows  = "+rowqty); //DEBUG

  // get the values - different syntax for a single cell vs range
  if (rowqty !=1){
    // Multiple cells - uset GetValues
    var rangevalues = rg1.getValues();
  }
  else {
    // single cell, use getValue
    var rangevalues = rg1.getValue();
  }
  //Logger.log("DEBUG: Values = "+rangevalues); //DEBUG

  // create array for temporary storage
  var newvalues = [];

  // loop through the values
  for (var i=0; i< rowqty; i++){

    // different treatment for single cell value
    if (i!=0 && rowqty !=1){
      // multiple cells
      var nstring = rangevalues[i].toString();
    }
    else {
      // single value cell
      var nstring = rangevalues.toString();
    }   
    Logger.log("DEBUG: Value of the string is = "+nstring); //DEBUG

    // slice the string in day, month and year
    var daystring = nstring.slice(0, 2);
    var monthstring = nstring.slice(2, 4);  
    var yearstring = nstring.slice(4, 8);

    //calculate the date
    var pubdate = new Date(yearstring, monthstring - 1, daystring);
    //Logger.log("DEBUG: the date is "+pubdate); //DEBUG

    // push the value onto the aray
    newvalues.push([pubdate]);

  }

  // set the value(s)
  if (rowqty !=1){
    // Multiple cells - uset GetValues
    rg1.setValues(newvalues)
  }
  else {
    // single cell, use getValue
    rg1.setValue(newvalues);
  }
  //rg1.setValues(newvalues);
  var message = "Update complete";
  rg1.setBackground('#ffffff');
  return message; 
}

pickRange.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <script>
     var grange='';
     function selectRange()
     {
       $('#btn1').prop('disabled',true);
       $('#btn2').prop('disabled',false);
       google.script.run
         .withSuccessHandler(setResponse)
         .selCurRng();
     }
     function setResponse(r)
     {
       grange=r;
       var msg='Selected range: '  + r+". Ready to convert"; 
       $('#instr').css('display','none');
       $('#rsp').text(msg); 
     }
     function convert2date()
     {
       $('#btn1').prop('disabled',false);
       $('#btn2').prop('disabled',false);
       google.script.run
         .withSuccessHandler(setResponse02)
         .convertnumbertodate(grange);
     }
     function setResponse02(q)
     {
       qnumber=q;
       var msg= q; 
       $('#instr').css('display','none');
       $('#rsp').text(msg); 
     }
     function clearAndClose()
     {
       google.script.run.clrRange(grange);
       google.script.host.close();

     }
     console.log('My Code');
    </script>
  </head>
  <body>
    <div id="rsp"></div>
    <div id="instr">Select range - <b>One column limit</b></div>
    <br/>
    <input type="button" id="btn1" value="1 - Select a range" onClick="selectRange();" />
    <br />
    <input type="button" id="btn3" value="2 - Convert numbers to dates" onClick="convert2date();" />
    <br />
    <input type="button" id="btn2" value="close" onClick="clearAndClose();"; disabled="true" />
  </body>
</html>

Credit
//Prompt user for range in .gs function, pass array to html script and re-focus on the HTML dialog //credit answer by Cooper - https://stackoverflow.com/a/45427670/1330560


ADDENDUM

If the range in which pseudo-dates are entered is know, and is non-changing, then the code to manage it is simplified

function onEdit(e) {

  // establish spreadsheet credentials
  var ss1 = SpreadsheetApp.getActive();
  var sh1 = ss1.getActiveSheet();

  // get the onEdit parameters
  var debug_e = {
    authMode: e.authMode,
    range: e.range.getA1Notation(),
    source: e.source.getId(),
    user: e.user,
    value: e.value,
    oldValue: e.oldValue
  };
  //Logger.log("AuthMode: "+debug_e.authMode+"\n, Range: "+debug_e.range+"\n, source: "+debug_e.source+"\n, user: "+debug_e.user+"\n, value: "+debug_e.value+"\n, old value: "+debug_e.oldValue);

  // Note the range for data entry is known and fixed.
  // it is "D3:D31"

  // Target range for converting numbers to dates
  // set the column
  var column = 4; // column D
  // get the first row and the number of rows
  var rowqty = 29;
  var rowfirst = 3;
  var rowlast = 31;
  //Logger.log("DEBUG: first row = "+rowfirst+", last row = "+rowlast+", number of rows = "+rowqty);//DEBUG

  // get detail of the  edited cell
  var editColumn = e.range.getColumn();
  var editRow = e.range.getRow();
  //Logger.log("DEBUG: edited column = "+editColumn+", edited row "+editRow);//DEBUG

  //test if the edited cell falls into the target range
  if (editColumn == 4 && editRow >= rowfirst && editRow <= rowlast) {
    // the edit was in the target range
    var nstring = e.value.toString();
    //Logger.log("DEBUG: Value of the string is = "+nstring); //DEBUG

    // slice the string in day, month and year
    var daystring = nstring.slice(0, 2);
    var monthstring = nstring.slice(2, 4);
    var yearstring = nstring.slice(4, 8);

    //calculate the date
    var pubdate = new Date(yearstring, monthstring - 1, daystring);
    //Logger.log("DEBUG: the date is "+pubdate); //DEBUG
    e.range.setValue(pubdate)

  } else {
    //Logger.log("DEBUG: Nothing to see here; this cell not in the target range");//DEBUG
  }
}
Tedinoz
  • 5,911
  • 3
  • 25
  • 35
  • So doesn't have a way to convert to date rightly only typing numbers in the cell? I don't want put divisors, like `/` or `-`. How can i edit my script to convert it in date? – Black Mamba Feb 19 '19 at 14:47
  • Your script is awsome! I'm very grateful for your help. Just look my edit, please. – Black Mamba Feb 20 '19 at 11:10
  • If is not too late, i just want check one point: If i write in the cell for example `26/02/2019` and then delet the date of this cell, the same cell will create automatically this date: `31/12/1969`. And i can't delet the date because it will generates it again. Can you plase fix it? – Black Mamba Feb 26 '19 at 18:19
  • As a matter of interest, why do YOU think it is happening? Have you done any research, testing or troubleshooting to understand the cause? I do recall that you said _I don't want put divisors, like..._, so I think it’s reasonable to expect that you should contribute to resolving this hiccup. – Tedinoz Feb 26 '19 at 19:21
  • I guess is because it memorize the wrong value for then convert to the date format. And if i delete it, it backs to the wrong value, i guess is the `e.oldValue`. I tried to delet it, also made some changes but nothing worked, so i didn't posted here – Black Mamba Feb 26 '19 at 19:27
  • If you type "14022019", the script converts an integer to a string, breaks it up ("14", "02", "2019") and uses the parameters to creates a new date. BUT, if you type "14/02/2019", the value is a date ("Thu Feb 14 2019 00:00:00") and the parameter break down ("Th", "u ", "Feb ") is meaningless. The "date" is invalid but Date command returns `Date(0)` and the resulting value is 1/1/1970 - the first day of the Javascript Epoch. If you accidentally type in 26/02/2019, or a blank for that matter, let the script do its thing; then just type in 26022019 and it should be OK. – Tedinoz Feb 27 '19 at 05:05