I'm writing a function that calculates how many days ago a given date was from today. (e.g. yesterday = 1, last week = 7, today = 0, tomorrow = -1 and so on)
Seemed simple enough, and using the JavaScript Date() function I initially wrote this:
let historicalDate = new Date(2017,05,17).getTime(); // example date: last week
let diff = Math.round((new Date().getTime() - historicalDate) / (24*60*60*1000) );
After I got some weird results, I neatened up the code, but still got the same issue, as follows:
/**
* Returns an integer, representing the number of days since a given date
**/
function getNumDaysFromDate(historicalDate){
const day = 24*60*60*1000; // The number of milliseconds in one day
const now = new Date().getTime(); // The time right now
const then = historicalDate.getTime(); // The time comparing to
return Math.round((now - then) / day ); // Find difference in milliseconds, then days
}
// Test1: last week, should return 7
let creationDate1 = new Date(2017,05,17);
console.log("Last week:", getNumDaysFromDate(creationDate1)); // Fail, prints -23
// Test2: yesterday, should return 1
let creationDate2 = new Date(2017,05,23);
console.log("Yesterday:", getNumDaysFromDate(creationDate2)); // Fail, prints -29
// Test3: Today, should return 0
let creationDate3 = new Date();
console.log("Today:", getNumDaysFromDate(creationDate3)); // Pass, prints 0
// Test4: day affer tomrrow, should return -2
let creationDate4 = new Date(2017,05,26);
console.log("Future:", getNumDaysFromDate(creationDate4)); // Fail, prints -32
All the above results appear to be all about 1 month out, (except for 'test 3', today).
I'm sure there is an obvious or simple reason for this, that one of you will spot instantly, but I have spent the last couple of hours mind-blown by it!
Thanks in advance!
Edit: If possible, I'd like to avoid using a library like Moment.js, as this should be possible nativity (?), and is the only date-related calc in my application.
Be careful: the Javascript date API is completely insane (exactly like the Java date API).
Month starts with 0 (January) and goes up to 11 (December). So new Date(2017,5,17) actually means June 17th 2017.
remind the months in JavaScript are zero based (Jan = 0, Feb = 1, ...). So if you need may it's 4 (not 5).
/**
* Returns an integer, representing the number of days since a given date
**/
function getNumDaysFromDate(historicalDate){
const day = 24*60*60*1000; // The number of milliseconds in one day
const now = new Date().getTime(); // The time right now
const then = historicalDate.getTime(); // The time comparing to
var value = Math.round((now - then) / day );
if(value == 0){
return 0
}
else{
return value+30;
}// Find difference in milliseconds, then days
}
// Test1: last week, should return 7
let creationDate1 = new Date(2017,04,17); // 17th of May 2017
console.log("Last week:", getNumDaysFromDate(creationDate1)); // Fail, prints -23
// Test2: yesterday, should return 1
let creationDate2 = new Date(2017,04,23); // 23 of May 2017
console.log("Yesterday:", getNumDaysFromDate(creationDate2)); // Fail, prints -29
// Test3: Today, should return 0
let creationDate3 = new Date();
console.log("Today:", getNumDaysFromDate(creationDate3)); // Pass, prints 0
// Test4: day affer tomrrow, should return -2
let creationDate4 = new Date(2017,04,26); // 26th of May 2017
console.log("Future:", getNumDaysFromDate(creationDate4)); // Fail, prints -32
Related
if I have an array of : ['Thursday', 'Friday']
and I want to generate 5 dates on the next dates of these days
for example, i want the result is the next Thursday is 14/7/2022, next Friday 15/7/2022
the output should be :
Thursday , Friday , Thursday , Friday , Thursday
=> output ['14/7/2022', '15/7/2022' , '21/7/2022' , '22/7/2022' , '28/7/2022']
If you can use libraries for that, then moment.js will help you a lot.
From that question we can create a solution for your case.
You can use moment.js to get a date from your string, and then using solution from question above get the date of that day of week on next week
This would be doable with two simple function in javascript
The first one would be to know the current date you are in ( & the day )
const d = new Date();
const day = d.getDay();
The getDay() method returns the day of the week (0 to 6) of a date.
Sunday = 0, Monday = 1, ... (See below):
More info here : https://www.w3schools.com/jsref/jsref_getday.asp.
Once you know the date, you would just need to convert your array from
['Thursday', 'Friday']
to [4,5]
Then you need to calculate the offset between the days
let offset = (targetDay - currentDayInNumber) % 7
if (offset < 0) { offset += 7 } // Want to make sure you offset is always positive
An example with tuesday = 2, thursday = 4, or monday = 1
let offset = (4 - 2) % 7 // ( 2 )
let offset = (1 - 2) % 7 // (-1)
offset = -1 + 7 // (6) we will be adding 6 days
Then you can simply loop and add days as you go
var date = new Date();
date.setDate(date.getDate() + days); // For tomorrow + 1 for example
Let me know if that helps, otherwise can provide you with the complete solution, but wanted to guide rather than give solution
-- Edit --
To complete this and to have the occurences, you could have a counter
const counterDays = {
0:0,
1:0,
..,
6:0
}
When going through the loop, everytime you setup a day, you increase the counter for it
This would be become something like this :
date.setDate(date.getDate() + offset + 7 * counterDays[targetDay]);
counterDays[targetDay]++;
I've provided you with the sample code here:
https://onecompiler.com/javascript/3y9sb8dqe
Hope this helps
Hobbyist coder here, and this problem is above my pay grade. I'm trying to build a dynamic html / css calendar, where the cells are filled in based on today's date. I get today's date, and then try to add days to fill in another 13 days (looping thru html elements.innerHTML).
If I try to setDate(30 + 2) and then getDate(). The code works fine. Javascript figures out that June ends at the 30th day, and the result is 2 as desired (July 2nd)
But this only works if there's only one call, if I have a loop, or call this code multiple times, then the result is different. Is there some async stuff gumming up the works? Here's code:
If you leave the "result2" call and comment the others, works great, but multiple calls, things break and numbers get repeated. Please help!
const theDate = new Date();
const todaysDate = 30;
theDate.setDate(todaysDate + 1);
let result1 = theDate.getDate();
theDate.setDate(todaysDate + 2);
let result2 = theDate.getDate();
theDate.setDate(todaysDate + 3);
let result3 = theDate.getDate();
theDate.setDate(todaysDate + 4);
let result4 = theDate.getDate();
console.log(result1);
console.log(result2);
console.log(result3);
console.log(result4);
June has 30 days but July has 31 days.
When you set the date to 32 for the first time, you are setting it to the 32nd of June and the dates after June 30 push it to July 2nd. (32-30=2)
When you set to 32 again, it is already July so the dates after July 31 push it to August 1st (32-31=1).
In answer to your question, the setDate() function is behaving so strangely for you because each time you are setting the date you are setting it relative to the previous setting, so incrementing each time by 31, 32, or 33 days instead of by 1, 2, or 3. See the brilliant answer by #Quentin for more information, this finding was entirely his and I just wanted to mention the root cause in my answer as well as my own fix to your problem.
An alternative solution if you just want to generate the dates:
const dayOfMonth = 30;
const date = new Date();
date.setDate(dayOfMonth);
console.log("Date:", date);
let timestamp = Date.parse(date);
for (let i = 1; i <= 14; i++) {
const newTimestamp = timestamp + i * (1000 * 60 * 60 * 24);
const newDate = new Date(newTimestamp);
console.log("New date:", newDate);
}
This method will manipulate the timestamp and generate new dates for each of the timestamps added to the number of milliseconds in a day.
You could use your date logic within the loop to populate the calendar as you mentioned.
If you use the Date() constructor on each iteration, you don't have to worry about the varying days of a particular month.
Details are commented in example
/**
* #desc - return a range of dates starting today (or a given
* date) and a given number of days (including start)
* #param {number} range - The number of days
* #param {string<date>} start - The date to start the range
* if not defined #default is today
* #return {array<date>} An array of dates
*/
function dayRange(range, start) {
// If undefined default is today
let now = start ? new Date(start) : new Date();
// Create an array of empty slots - .length === range
let rng = [...new Array(range)];
/*
.map() through array rng
If it's the first iteration add today's date...
... otherwise get tommorow's date...
and return it in local format
*/
return rng.map((_, i) => {
if (i === 0) {
return now.toLocaleDateString();
}
let day = now.getDate() + 1;
now.setDate(day);
return now.toLocaleDateString();
});
}
console.log("Pass the first parameter if the start day is today");
console.log(JSON.stringify(dayRange(14)));
console.log("Pass a properly formatted date string as the second parameter if you want to start on a date other than today");
console.log(JSON.stringify(dayRange(10, '05/12/2020')));
I have a sheet with a time trigger set to run every 30 minutes. When the trigger happens a function is executed which will add a new row at the bottom of the sheet with some data.
On column A I have dates.
Now the problem is sometimes the Google's trigger tool by error will execute like 3 times in a row or more with less then a minute in between each execution. This happens more often than I'd like and I need a way to fix this.
I wrote some code which supposedly will delete the new recorded row if the difference between this last row and the second last row, or previous row, is less than 30 minutes. This way all the rows will always be 30 minutes apart from each other.
I'm stuck at this point where I can't figure out a way of making Google Script to compare 2 dates and return TRUE or FALSE based on my condition, which is to check if the difference between 2 dates is more/equal or less than 30 minutes, and if it is less to delete the row, otherwise do nothing. Actually I gave the condition a margin of 1 minutes because the triggers are not 100% exact and don't always happen at the same second.
The variable timerDifference returns NaN.
I suspect it might be because of the date format?
This is my code ATM:
function deleteTriggerError() {
let logSheetName = "LOG";
let logSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(logSheetName);
let lastRowTimer = logSheet.getRange(logSheet.getLastRow(), 1).getValue();
let secondLastRowTimer = logSheet.getRange(logSheet.getLastRow() - 1, 1).getValue();
console.log(lastRowTimer);
console.log(secondLastRowTimer);
let dysLast = Utilities.formatDate(lastRowTimer, timeZone, 'dd/mm/yyyy hh:mm:ss');
let dysSecondLast = Utilities.formatDate(secondLastRowTimer, timeZone, 'dd/mm/yyyy hh:mm:ss');
console.log(dysSecondLast);
console.log(dysLast);
let timerDifference = dysLast - dysSecondLast;
console.log(timerDifference);
let timerDifLimitRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(logSheetName).getRange("H3");
let timerDifLimitValueTXT = timerDifLimitRange.getValue();
let timerDifLimitValue;
//console.log(timerDifference);
timerDifLimitValue = timerDifLimitValueTXT.replace("3 0 M I N U T E S", 30 - 1);
logSheet.appendRow([""]);
if (timerDifference < timerDifLimitValue) {
logSheet.deleteRow(logSheet.getLastRow());
// console.log("TRUE");
} else {
// console.log("FALSE");
}
}
I tried the solution I saw here:
Time difference between two time showing wrong values in js
var diff = Math.abs(new Date('01/23/2020 06:30 PM') - new Date('01/23/2020 05:00 AM'));
var minutes = Math.floor((diff/1000)/60);
alert(minutes);
This solution will only work with en_US date format. But I'm using en_GB date format:21/09/2021 14:44:38. Any reason why?
You can check:
var diff = Math.abs(new Date('01/23/2020 06:30 PM') - new Date('01/23/2020 05:00 AM'));
console.log(diff);
var minutes = Math.floor((diff/1000)/60);
console.log(minutes);
var diff = Math.abs(new Date('21/09/2021 14:44:38') - new Date('21/09/2021 14:04:38'));
console.log(diff);
var minutes = Math.floor((diff/1000)/60);
console.log(minutes);
var diff = Math.abs(new Date('09/21/2021 14:44:38') - new Date('09/21/2021 14:04:38'));
console.log(diff);
var minutes = Math.floor((diff/1000)/60);
console.log(minutes);
Thank you for your time.
My file:
https://docs.google.com/spreadsheets/d/1ExXtmQ8nyuV1o_UtabVJ-TifIbORItFMWjtN6ZlruWc/edit?usp=sharing
Date() doesn't support dd/mm/yyyy. This prevents ambiguity for cases like 1/2/2014 that yields into 2 possible dates, Jan 2 and Feb 1. So it only supports the mm/dd/yyyy as its standard format.
One way to converting it properly is to split the date.
function myFunction() {
startDate = '21/09/2021 14:44:38';
endDate = '21/09/2021 14:04:38';
var diff = Math.abs(convertGBDatetoDate(startDate) - convertGBDatetoDate(endDate));
console.log(diff);
var minutes = Math.floor((diff/1000)/60);
console.log(minutes);
}
function convertGBDatetoDate(string){
var [sD, sM, sY] = string.split(' ')[0].split('/').map(i=>Number(i));
var [sh, sm, ss] = string.split(' ')[1].split(':').map(i=>Number(i));
return new Date(sY,sM - 1,sD,sh,sm,ss);
}
Someone commented above, but then deleted, to use getTime().
Read here.
So I think this works. I don't even need to worry about date formats. I can just get the range with getRange() and getValue(). Then I can use this simple code:
var end, start;
start = new Date("Tue Sep 21 2021 20:00:00 GMT+0100 (British Summer Time)");
end = new Date("Tue Sep 21 2021 20:33:17 GMT+0100 (British Summer Time)");
console.log('Operation took ' + (((end.getTime() - start.getTime())/1000)/60) + ' min');
I have days, months and years. I'm doing calculations between them. That means I have to divide 2 years 3 months and 10 days by 1/4. Now i have following code:
const getCurrentDate = moment().format("YYYY-MM-DD");
const timeEnd = moment(moment(DefEndDate).format("YYYY-MM-DD"));
const diff = timeEnd.diff(getCurrentDate);
const diffDuration = moment.duration(diff);
const diffCount = moment.duration(diff).asDays();
console.log(diffCount);
console.log("Years:", diffDuration.years());
console.log("Month:", diffDuration.months());
console.log("Days:", diffDuration.days());
const diffCount = moment.duration(diff).asDays(); //Get it as days
const [unserve, setUnserve] = useState(''); //set value to variable
const res = unserve.split('/'); //split 1/4 to 1.4
const x = parseFloat(res[0] + "." + res[1]); //convert it to float
var quotient = Math.floor(diffCount/x); //calculate
console.log(quotient);
//returned 832 / 1.4 = 594 days
Now I need to return the output number (days) to the year, month and day. I can't do that. How do I convert? And another question is, can this way be the optimal solution?
I can't decide whether what you really want to do is divide a date range in to a fixed number of periods with equal days, or to start with a date, add a period in years, months and days to get an end date, then divide that into equal periods.
The following assumes the latter.
I have to divide 2 years 3 months and 10 days by 1/4
The number of days covered by that period varies depending the dates it is to and from, so you have to start with the start and end dates of the range.
In your code:
const getCurrentDate = moment().format("YYYY-MM-DD");
Sets getCurrentDate to a string like 2020-02-11.
const timeEnd = moment(moment(DefEndDate).format("YYYY-MM-DD"));
Creates a moment object from the string value of getCurrentDate and sets timeEnd to another string.
const diff = timeEnd.diff(getCurrentDate);
This attempts to call the diff method of timeEnd, which is a string. Strings don't have a diff method so the expression returns undefined, attempting to call it throws an error something like TypeError: '2020-02-11'.diff is not a function.
The rest of your code seems to be based on a algorithm
If you have a predetermined period in years, months days, etc. you can start with a start date, add the period, then get the number of days difference. Divide that difference by the number of periods you want, then add that sequentially to get the various end dates.
The following example uses moment.js since that's what you appear to be using, however a version without a library is about the same difficulty. It returns an array of dates, starting with the start date so there is one more date than periods.
function getDates(
start = new Date(),
years = 0,
months = 0,
days = 0,
parts = 1) {
// Get start and end as moment objects
let m = moment(start).startOf('day');
let end = moment(m);
end.add({years:years, months:months, days:days});
// Get days difference and number of days to add for each period
let daysDiff = end.diff(m, 'days');
let f = daysDiff / parts;
let dayArray = [m.format('YYYY-MM-DD')];
let i = 0;
while ((f * ++i) <= daysDiff) {
let d = moment(m).add(f * i, 'days')
dayArray.push(d.format('YYYY-MM-DD'));
}
return dayArray;
}
// Get dates for 4 even periods over 2 years, 3 months and
// 10 days from today
console.log(getDates(new Date(), 2, 3, 10, 4));
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
As the sub–period length is nearly always not an even number of days (in the above case it's about 207.5), I've allowed formatting to effectively truncate the decimal part of a day. You might want to use some other rounding algorithm that more evenly distributes the remainder.
If, on the other hand, you have start and end dates and want an equal number of periods, the following is much simpler (and doesn't use a library):
// Helpers - use UTC do avoid DST issues
function toUTCDate(s) {
let b = s.split(/\D/);
return new Date(Date.UTC(b[0], --b[1], b[2]));
}
function formatUTC(date) {
return date.toISOString().substr(0, 10);
}
/* #param {string} start - date string in format 'YYYY-MM-DD'
** #param {string} end - date string in format 'YYYY-MM-DD'
** #param (numbe} n - number of required periods
** #returns {Array} array of date strings in format 'YYYY-MM-DD'
*/
function periods(start, end, n) {
let s = toUTCDate(start);
let e = toUTCDate(end);
let diff = e - s;
let f = diff / n;
let result = [formatUTC(s)];
// Allow for rounding of decimal f in comparison
while (e - s > n) {
s.setTime(s.getTime() + f);
result.push(formatUTC(s))
}
return result;
}
console.log(periods('2020-02-09','2022-05-19', 4));
The two methods produce slightly different results, you'll need to work out if that matters or not.
Long time ago I used such code to convert from seconds to hours, minutes and seconds:
var hours = Math.floor(sec / 3600);
var minutes = Math.floor((sec - hours * 3600) / 60);
var seconds = Math.round(sec - hours * 3600 - minutes * 60);
Math with days, months and years should be somewhat similar but my rusty brain doesn't want to think about it
UPD
This function seems to do what you need
const daysFmt = days => {
const years = Math.floor(days / 365);
const months = Math.floor((days - years * 365) / 30);
const d = Math.round(days - years * 365 - months * 30);
let res = [];
if (years > 0) {
res.push(years + ' y');
}
if (months > 0) {
res.push(months + ' m');
}
if (d > 0) {
res.push(d + ' d');
}
return res.join(', ');
}
But this solution has one nuance: it assumes that month = 30 days. You might want to add if statement to return '1 m' if input is 31 or just return number of days if it is less than 32. Test results:
daysFmt(31);
"1 m, 1 d"
daysFmt(180);
"6 m"
daysFmt(185);
"6 m, 5 d"
daysFmt(356);
"11 m, 26 d"
daysFmt(365);
"1 y"
daysFmt(420);
"1 y, 1 m, 25 d"
daysFmt(3650);
"10 y"
daysFmt(3685);
"10 y, 1 m, 5 d"
I've seen a lot of functions to convert dates around but couldn't find anything specific on how to convert Days:Hours:Minutes:Seconds to milliseconds.
So here is a basic function I've made to help you guys out. This is useful if you're coding a stopwatch, clock or anything like that.
Normally I've seen this done inline without using a utility function, but if you're going to create a util let's make it extensible.
I disagree with the arguments Array, it's difficult to remember what represents what. Unless you're only doing day/hour/minute/second, this can get confusing. Additionally, unless you're always using every parameter this becomes cumbersome.
It's incorrect for zero values (passing 0 for any value causes it to be incorrect)
const conversionTable = {
seconds: 1000,
minutes: 60*1000,
hours: 60*60*1000,
days: 24*60*60*1000,
};
const convertTime = (opts) =>
Object.keys(opts).reduce((fin, timeKey) => (
fin + opts[timeKey] * conversionTable[timeKey]
), 0)
console.log(convertTime({
days: 5,
hours: 4,
minutes: 2,
seconds: 19,
}));
console.log(convertTime({seconds: 1}));
function convertDhms(d,h,m,s){
d <= 0 ? d=1 : d=d*24*60*60*1000;
h <= 0 ? h=1 : h=h*60*60*1000;
m <= 0 ? m=1 : m=m*60*1000;
s <= 0 ? s=1 : s=s*1000;
return d + h + m + s;
}
Usage:
var finalDate = convertDhms(5, 4, 2, 19); /* will convert 5 days, 4 hours, 2 minutes and 19 seconds to miliseconds. Keep in mind that the limit to hours is 23, minutes 59 and seconds 59. Days have no limits. */
I suppose a simple solution is to use the Date object's parse method, which gives back the milliseconds of the object. The catch is that it's meant to return the time from the UNIX Epoch time.
// see docs for Date constructor
const baseDate = new Date(0,0,0,0,0,0,0);
const baseMS = Date.parse(baseDate);
// base milliseconds is not zero
// it defaults to a day before Jan 1, 1970 in ms
console.log(baseMS);
function convertToMS(dy,hr,mn,s,ms) {
const date = new Date(0,0,dy,hr,mn,s,ms);
const dateMS = Date.parse(date);
return dateMS - baseMS;
}
// one day in milliseconds
console.log(convertToMS(1,0,0,0,0));
console.log(24 * 60 * 60 * 1000);
P.S. I don't quite understand the logic behind why a new Date object with zero in all parameters returns a large negative value, but we have to account for that in the code.
EDIT: Since there's is a discrepancy between the number of days in each month, and days in each year, it's better to not have year and months in the input of the function convertToMS.