Google sheet date of last revision in a row - date

Help!
In Google sheets, i just want one column to input the last date and time that any cell in a row is updated or revised.
For example:
a1 - should get the last revision date,
b1-z1 (any number of cells after a1) - everytime any cell in that row is updated or revised, a1 date is updated.
I want to apply the same script to more than one sheet.
I've found this script, which seemed to work fine, initially:
function onEdit(event){
var ss = SpreadsheetApp.getActiveSpreadsheet();
//Script Last Update Timing
var actSht = event.source.getActiveSheet();
var actRng = event.source.getActiveRange();
var activeCell = actSht.getActiveCell();
var row = activeCell.getRow();
var column = activeCell.getColumn();
if(row < 2) return; //If header row then return
var colNums = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]; //Coulmns, whose edit is considered
if(colNums.indexOf(column) == -1) return; //If column other than considered then return
var index = actRng.getRowIndex();
var dateCol = actSht.getLastColumn();
var lastCell = actSht.getRange(index,dateCol);
var date = Utilities.formatDate(new Date(), "GMT+5:30", "dd/MM/yyyy HH:mm:ss");
// Note: Insert the Date when someone update the row in the last coulmn
lastCell.setValue(date);
}
When I was testing it, it worked fine, but after that day it wasn't working or sometimes it works, or works when I do revisions myself but it doesn't work with the person/s I am sharing the file with.
I don't know anything about scripting but after a week of searching, I have opted to post, hoping someone can lead me to the right direction.
Any help would be greatly appreciated! Thank you in advance!

See if this helps:
function onEdit(e) {
if (e.range.columnStart < 5 || e.range.rowStart < 21) return;
e.source.getActiveSheet().getRange(e.range.rowStart, 1) //first col
.setValue(Utilities.formatDate(new Date(), "GMT+5:30", "dd/MM/yyyy HH:mm:ss"));
}

Related

Dates in my spreadsheets keep getting converted to DateTimes and I want the time component removed as this is handled in a differnt column

I keep track of trading data in a private spreadsheet and, once a trade is closed, I push a button to run some AppsScript to copy the data across to a publicly visible spreadsheet. It all works well except the following point. I put the dates and times a trade is opened in two different columns and the same with the dates and times trades are closed. When I copy the dates across to the target spreadsheet, instead of "10 Jul 2022" it is showing as "10/07/2022 02:00:00". When I sort the spreadsheet on these date and time columns, this can often produce unwated results. What I want is for the date column to just display as "10 Jul 2022" and for that data to recognized as a date for sorting purposes. Is that possible?
function updatePublicSheet(){
var coinBought;
var dateBought;
var timeBought;
var priceBought;
var dateSold;
var timeSold;
var priceSold;
var sLrIndex;
var tLrIndex;
var sss = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1cBGpD0nUqCGtQqq78svw4fxA4ZgwBqV2yzIbgY1tY2o/edit#gid=1107419239");
var sSheet = sss.getSheetByName('SS Open Trades');
var sRng = sSheet.getRange("H3:J").getValues();
// Find last entry in columns H, I or J in the Open Trades sheet
for (var i = sRng.length-1;i>=0;i--){
var sLrIndex = i;
if (!sRng[i].every(function(c){return c == "";})){
break;
}
}
Logger.log("Last source row is %s", sLrIndex + 3);
// Check that the closed information has been added to all columns
// index 8 = 8th column = H = dateSold
// index 9 = 9th column = I = timeSold
// index 10 = 10th column = J = priceSold
if ((sSheet.getRange((sLrIndex + 3),8).getValue().length) == 0 ||
(sSheet.getRange((sLrIndex + 3),9).getValue().length) == 0 ||
(sSheet.getRange((sLrIndex + 3),10).getValue().length) == 0){
SpreadsheetApp.getUi().alert("You cannot process partially completed entries");
return;
}
// If there is a complete closed entry, copy the values to the variables
if (sLrIndex > 0)
{
coinBought = sSheet.getRange((sLrIndex + 3),2).getValue();
dateBought = toUtcString(sSheet.getRange((sLrIndex + 3),4).getValue());
timeBought = sSheet.getRange((sLrIndex + 3),5).getValue();
priceBought = sSheet.getRange((sLrIndex + 3),6).getValue();
dateSold = toUtcString(sSheet.getRange((sLrIndex + 3),8).getValue());
timeSold = sSheet.getRange((sLrIndex + 3),9).getValue();
priceSold = sSheet.getRange((sLrIndex + 3),10).getValue();
//Logger.log(priceSold);
}
else
{
SpreadsheetApp.getUi().alert("There are no completed entries to process");
return;
}
var tss = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1umkTCr95FZUrZzv0e_ZDD9QZYiiYuH1fJohPYQzNE9Q/edit#gid=1644116137");
var tSheet = tss.getSheetByName('Trade Tracker');
var tRng = tSheet.getRange("B3:J").getValues();
// Create a new row at Row 5 on the Trades Tracker Sheet
tSheet.insertRows(5, 1);//shift all rows down by one from row 5
// Copy values from row 4 to row 5, including the formulae
var tRange = tSheet.getRange(4, 1, 1, 26);
tRange.copyTo(tSheet.getRange(5, 1, 1, 26), {contentsOnly:false});
// Populate row 4, first 7 table columns, from the variables
//Logger.log(coinBought);
tSheet.getRange(4,2).setValue(coinBought);
tSheet.getRange(4,3).setValue(dateBought);
tSheet.getRange(4,4).setValue(timeBought);
tSheet.getRange(4,5).setValue(priceBought);
tSheet.getRange(4,6).setValue(dateSold);
tSheet.getRange(4,7).setValue(timeSold);
tSheet.getRange(4,8).setValue(priceSold);
// Format the cells with dates and times
tSheet.getRange(4,3).setNumberFormat("dd MMM yyyy"); // Short Date
tSheet.getRange(4,4).setNumberFormat("HH:mm"); // Short Time
tSheet.getRange(4,6).setNumberFormat("dd MMM yyyy"); // Short Date
tSheet.getRange(4,7).setNumberFormat("HH:mm"); // Short Time
// Sort the sheet by date/time closed
// Find last entry in Trade Tracker sheet
for (var i = tRng.length-1;i>=0;i--){
var tLrIndex = i;
if (!tRng[i].every(function(c){return c == "";})){
break;
}
}
Logger.log("Last target row is %s", tLrIndex + 4);
var tRange = tSheet.getRange(4, 2, tLrIndex + 4, 8)
tRange.sort([{column: 6, ascending: false}, {column: 7, ascending: false}]);
// On the source sheet, delete the row just copied and add another blank row at the bottom of the table
sSheet.deleteRows(sLrIndex + 3, 1);
var rowLast = sSheet.getLastRow();
sSheet.insertRowAfter(rowLast - 1);
// Copy values from the new last row to the new previous to last row, including the formulae
var sRange = sSheet.getRange(rowLast + 1, 1, 1, 10);
sRange.copyTo(sSheet.getRange(rowLast - 1, 1, 1, 10), {contentsOnly:false});
SpreadsheetApp.getUi().alert("One completed entry processed");
}

How to extract a specific timeframe from datetime?

I have the time in datetime format like below. I would like to extract the time from '20-Apr-2020 11:20:10' till '20-Apr-2020 12:40:50'. Do I need it to convert it first to datenumber or I can do it directly here?
Time_datenum={'20-Apr-2020 11:06:00','20-Apr-2020 11:20:10','20-Apr-2020 11:45:30','20-Apr-
2020 12:07:00','20-Apr-2020 12:35:40','20-Apr-2020 12:40:50','20-Apr-2020 13:07:00'};
Time_datetime = datetime(Time_One,'InputFormat','dd-MM-yyyy HH:mm:ss');
Time_datenum={'20-Apr-2020 11:06:00','20-Apr-2020 11:20:10','20-Apr-2020 11:45:30',...
'20-Apr-2020 12:07:00','20-Apr-2020 12:35:40','20-Apr-2020 12:40:50','20-Apr-2020 13:07:00'};
% Create a datetime array from a cell array of character vectors.
Time_datetime = datetime(Time_datenum, 'InputFormat', 'dd-MM-yyyy HH:mm:ss', 'Locale', 'en_GB');
% t = datetime(Year, Month, Day, Hour, Minute, Second)
Time_start = datetime(2020, 4, 20, 11, 20, 10);
Time_end = datetime(2020, 4, 20, 12, 40, 50);
% Extract the time.
Time_extracted = Time_datetime(Time_start <= Time_datetime & Time_datetime <= Time_end);

The ten's digit and unit's digit of numbers

I have this code
int[,] array = new int[,]{ {34, 21, 32, 41, 25},
{14 ,42, 43, 14, 31},
{54, 45, 52, 42, 23},
{33, 15, 51, 31, 35},
{21, 52, 33, 13, 23} };
for (int i = 0; i < array.GetLength(1); i++)
{
for (int j = 0; j < array.GetLength(0); j++)
{
Console.Write(array[i, j] + " ");
}
Console.WriteLine();
}
and i need to find a specific number ( the treasure ).
For each value the ten's digit represents the row number and the unit's digit represents the column number of the cell containing the next clue.
Starting in the upper left corner (at 1,1), i have to use the clues to guide me search of the array. (The first three clues are 11, 34, 42).
The treasure is a cell whose value is the same as its coordinates.
The program should output the cells it visits during its search.
I did the simply way:
Console.WriteLine("The next clue is: {0}", array[0, 0]);
Console.WriteLine("The next clue is: {0}", array[2, 3]);
Console.WriteLine("The next clue is: {0}", array[3, 2]);
Console.WriteLine("The next clue is: {0}", array[0, 4]);
and so on, but the problem is, that if I change the array to set another route the program will output the wrong way. So the solution needs to be dynamic and find the treasure regardless of the array content.
My problem is that i don't know how to do to find the ten's digit of the numbers and the unit's digit.
Can anyone please help me with this?
To illustrate my comment: code below and Fiddle
(I've added a HashSet<int> to track which cells have already been visited and avoid ending up with an infinite loop)
int[,] array = new int[,]
{
{34, 21, 32, 41, 25},
{14 ,42, 43, 14, 31},
{54, 45, 52, 42, 23},
{33, 15, 51, 31, 35},
{21, 52, 33, 13, 23}
};
int currentCoordinates = 11;
bool treasureFound = false;
var visitedCells = new HashSet<int>();
while (!treasureFound && !visitedCells.Contains(currentCoordinates))
{
int currentRowIndex = currentCoordinates / 10;
int currentColumnIndex = currentCoordinates % 10;
int nextCoordinates = array[currentRowIndex - 1, currentColumnIndex - 1];
if (nextCoordinates == currentCoordinates)
{
treasureFound = true;
}
else
{
visitedCells.Add(currentCoordinates);
currentCoordinates = nextCoordinates;
}
}
if (treasureFound)
{
Console.WriteLine($"Treasure found in cell {currentCoordinates}");
}
else
{
Console.WriteLine("No treasure");
}

Sort numbers inside string

I have a string that consists of few numbers. First number is the number of the row, remaining are numbers in this row.(Array but string, kind of). The problem is that remaining numbers are unsorted, and I want to find the clearest way of sorting them without creating new List and sorting everything there.
String unsorted = '9, 12, 14, 11, 2, 10';
print(unsorted.sort()); // '9, 2, 10, 11, 12, 14'
You cant really avoid converting to a list of numbers if you want to sort it.
void main() {
print(sortNumString('9, 12, 14, 11, 2, 10')); // 2, 9, 10, 11, 12, 14
}
String sortNumString(String numString, [String separator = ', ']) =>
(numString.split(separator).map(int.parse).toList()..sort())
.join(separator);
The .. means to return the previous thing, the list, since sort returns void.
I'm not exactly experienced when it comes to dart programming language but this is what i came up with.
void main() {
var unsorted = "9, 12, 14, 11, 2, 10";
var nums_int = unsorted.split(", ").map(int.parse).toList();
nums_int.sort();
for (var n in nums_int) {
stdout.write(n.toString() + ", ");
}
}
That should give the expected output: "2, 9, 10, 11, 12, 14"
Hope this helps.

Int96Value to Date string

When reading a parquet file (using Scala) I read the timestamp field back as:
Int96Value{Binary{12 constant bytes, [0, 44, 84, 119, 54, 49, 0, 0, -62, -127, 37, 0]}}
How can I convert it to a date string?
I did some research for you. The Int96 format is quite specific a seems to be deprecated.
Here is a discussion about converting Int96 to Date.
Based on this, I created following piece of code:
def main(args: Array[String]): Unit = {
import java.util.Date
import org.apache.parquet.example.data.simple.{Int96Value, NanoTime}
import org.apache.parquet.io.api.Binary
val int96Value = new Int96Value(Binary.fromConstantByteArray(Array(0, 44, 84, 119, 54, 49, 0, 0, -62, -127, 37, 0)))
val nanoTime = NanoTime.fromInt96(int96Value)
val nanosecondsSinceUnixEpoch = (nanoTime.getJulianDay - 2440588) * (86400 * 1000 * 1000 * 1000) + nanoTime.getTimeOfDayNanos
val date = new Date(nanosecondsSinceUnixEpoch / (1000 * 1000))
println(date)
}
However, it prints Sun Sep 27 17:05:55 CEST 2093. I am not sure, if this is a date, that you expected.
Edit: using Instance as suggested:
val nanosInSecond = 1000 * 1000 * 1000;
val instant = Instant.ofEpochSecond(nanosecondsSinceUnixEpoch / nanosInSecond, nanosecondsSinceUnixEpoch % nanosInSecond)
println(instant) // prints 2093-09-27T15:05:55.933865216Z
java.time supports Julian days.
Credits to ygor for doing the research and finding out how to interpret the 12 bytes of your array.
byte[] int96Bytes = { 0, 44, 84, 119, 54, 49, 0, 0, -62, -127, 37, 0 };
// Find Julian day
int julianDay = 0;
int index = int96Bytes.length;
while (index > 8) {
index--;
julianDay <<= 8;
julianDay += int96Bytes[index] & 0xFF;
}
// Find nanos since midday (since Julian days start at midday)
long nanos = 0;
// Continue from the index we got to
while (index > 0) {
index--;
nanos <<= 8;
nanos += int96Bytes[index] & 0xFF;
}
LocalDateTime timestamp = LocalDate.MIN
.with(JulianFields.JULIAN_DAY, julianDay)
.atTime(LocalTime.NOON)
.plusNanos(nanos);
System.out.println("Timestamp: " + timestamp);
This prints:
Timestamp: 2017-10-24T03:01:50
I’m not happy about converting your byte array to an int and a long by hand, but I don’t know Parquet will enough to use the conversions that are probably available there. Use them if you can.
It doesn’t matter which LocalDate we use as starting point since we are changing it to the right Julian day anyway, so I picked LocalDate.MIN just to pick one.
The way I read the documentation, Julian days are always in the local time zone, that is, no time zone is understood, and they always start at midday (not midnight).
Link: Documentation of JulianFields in java.time