I have two dates in JavaScript, start and finish, parsed by Moment.js. What would be the most efficient way to swap them if start is posterior to finish without having to create a third date? Typically, the dates would be parameters of a function like this one:
function getDates(start, finish) {
var start_date = moment(new Date(start));
var finish_date = moment(new Date(finish));
if (start_date.diff(finish_date) > 0) {
// Swap code goes here...
}
}
The reason why one might want to swap dates is because some functions are defined in such a way that the same result is returned even if the dates are swapped, yet the function needs to know which date is the earlier one. For example, YEARFRAC in Microsoft Excel works that way. You can see my implementation of that function here for an example of a sub-optimal date swapping.
Thanks in advance for your help!
What would be the most efficient way to swap them if start is posterior to finish
With a third variable. For fun, you could use a destructuring assignment to prevent that work syntactically around that, but this will hardly make a performance difference.
without having to create a third date?
That's the point of using a variable. With the binary operator way you mentioned you would need to cast the Date object to an integer, apply the operation, and then create two new Date objects from the integers.
If your constraint against having an additional Date should be interpreted as not having another variable at all, then I have a solution based on this person's brilliant idea for inline variable swapping:
function getDates(start, finish) {
var start_date = new Date(2013, 0, 5);
var finish_date = new Date(2013, 0, 4);
start_date = start_date.getTime();
finish_date = finish_date.getTime();
start_date = -(finish_date = (start_date += finish_date) - finish_date) + start_date;
start_date = new Date(start_date);
finish_date = new Date(finish_date);
alert(start_date);
alert(finish_date);
}
I'm not sure to have understood what you need but, if you pass the timestamp to start and finish, with something like this:
start_date = start > finish ? finish : start;
finish_date = start > finish ? start : finish;
you swap the values without the third temporary variable. Then you can create the moment objects and work with them. For example:
function dateDiff( start, finish, measure ){
var start_date = start > finish ? finish : start,
finish_date = start > finish ? start : finish;
return moment(finish_date).diff(moment(start_date), measure);
}
dateDiff( new Date(2013,1,21), new Date(2013,1,28), 'days' ) // 7
dateDiff( new Date(2013,1,28), new Date(2013,1,21), 'days' ) // 7
If you need only to know the positive difference you can even do:
date_diff = (start < finish ? finish : start) - (start < finish ? start : finish);
EDIT:
However, it's possible to test that even with ternary operators the performance is lower than the classic if..else statement.
Hope this might be of some help.
Related
I have this webpage that takes in an input in seconds, and produces what the time will be after the time has elapsed. So I went about it using the following code
let duration = prompt("Enter your trip duration in seconds: "); // get the time from the vaue of the input element
let newtime = new Date(); // get the current time
newtime.setSeconds(newtime.getSeconds() + duration)
I decided to test the code and it gave me weird results and for some reason when I divided the duration by ten it started to work. Even though it worked I wasn't satisfied. So I added some more code to try and find where the bug is.
let oldtime = new Date();
oldtime.setSeconds(oldtime.getSeconds() + 60);
I added some code so that it would show on the webpage to compare the two times and these are my results when I enter 60 as the duration...
Just a little summary. The code works when I hard code the values in but it behaves weirdly if I try get the input from a prompt
You are concatenating a string to a number instead of adding. You have to parse the result from prompt to a number (with the unary plus operator):
let duration = prompt("Enter your trip duration in seconds: "); // get the time from the vaue of the input element
let newtime = new Date(); // get the current time
newtime.setSeconds(newtime.getSeconds() + +duration)
console.log(newtime)
I believe the issue is because you need to parse the duration from a string to number. So, try this
newtime.setSeconds(newtime.getSeconds() + parseInt(duration, 10))
You can use a library like Luxon to deal with dates and times without having to worry about unexpected behaviour like the one you described.
import { DateTime } from "luxon";
const now = DateTime.now();
const timeAfterTrip.plus({ seconds: duration });
console.log(timeAfterTrip);
I'm trying to make a function to set some contents on a cell of a spreadsheet from a data set by user through a prompt.
There are some parts working well and some problems
To put in context, we have a spreadsheet with a "calendar" as follows: every cell in the A:A range is a day of the school year, starting from September/1 in A1, September/2 in A2, and so. In the B column we have for now the holidays, vacation period and other. There are many "stable" days in a school year, such as Christmas day: every year is the same day and month (only changes year). Besides this, there are some days every school (almost in Spain) choose to get a little break.
First, we set function, input range (A column to compare) and output range (B columne, where we want to write new content). (the var year is given as an argument of the function, but is set here for testing)
function setSpecialHolidays() {
var year = 2017;
I had to make a parseInt for this data, because when given as an argument detects it as a string and doesn't work well. But now is ok.
var yearNumber = parseInt(year);
var sheet = SpreadsheetApp.getActive().getSheetByName("Calendar"+year);
var inputRange = sheet.getRange('A1:A'+sheet.getLastRow()).getValues();
var outputRange = sheet.getRange('B1:B'+sheet.getLastRow());
Here start a loop where we can input different days. Every day I set in the prompt the script has to set it in a cell corresponding the date and the content of the A column
I set this ButtonSet before the loop because I need it to start the while, as it will be still working until the user clicks "No" button.
var specialHoliday = ui.alert('Click «Yes» to set a special holidays', ui.ButtonSet.YES_NO);
while (specialHoliday == ui.Button.YES) {
var dayPointer = ui.prompt('Please enter the special holiday date (DD/MM)');
var specialHolidayDate = dayPointer.getResponseText();
var specialHolidayDateParts = specialHolidayDate.split("/");
var date = new Date();
date.setMonth(specialHolidayDateParts[1] - 1, specialHolidayDateParts[0]);
date.setHours(0, 0, 0, 0);
As the school year comprend two different natural years, to make possible Date compare, we need to ensure that if the month is from September to December is the current year, but if the month is from January to July or so is the *current year + 1", this is, the next year. Because of that I make this conditional. This is working well too. I've made some tests and ui.alert to be ensured and detects well the year, if current or next.
if (specialHolidayDateParts[1] <= 8) {
date.setYear(yearNumber + 1);
} else if (specialHolidayDateParts[1] > 8) {
date.setYear(yearNumber);
}
Here is were doesn't work. Once we have set correctly the input date we can start compare it with all values in the inputRange. Then, look every A column cell and, when find a coincidence set in the outputRange the string.
I think there's probably a better solution to this problem, maybe doesn't making a loop, but this is a proccess I've followed before in other functions of this script and that's well for now. This specific date setting is the last step of our program (for now), but I'm
for (var i = 0; i < inputRange.length; i++) {
var dateCompare = new Date(inputRange[i][0]);
if (dateCompare == date) {
outputRange[i][0].setValue("Holiday: special holiday");
}
}
Finally, if user wants to set another day, ButtonSet allows it. When users finish click "NO" instead of yes and everything stops. That's working too.
var specialHoliday = ui.alert('Do you want to set a new special holiday?', ui.ButtonSet.YES_NO);
}
Well, thanks in advance for help.
(Posted on behalf of the OP).
I've made some tries and finally I've found the correct way to do what I want, to set a value in a cell next to a cell that have a date I've compared from a user input date. There was a mistake in the statement:
In a range I can't setValues (but I was trying...)
Is needed to use the .getTime() method on compare the date values
Here is the (I think!) corrected code for the for loop:
for (var i = 0; i < inputRange.length; i++) {
var dateCompare = new Date(inputRange[i][0]);
if (dateCompare.getTime() == date.getTime()) {
outputRange[i][0] = "Holiday: special holiday";
}
}
I finally understood the problem in outputRange: as a range, I can't setValues, because is not a cell, then, I've to assign the value as is. What I'm not sure to get is why I need the .getTime() after every compared value. But it works!
I'm trying to decrement a variable once a day. I have written the following code for that.
var counter = 10; //any value
setInterval(function() {
counter = counter - 1;
}, 86400000);
Is there a better or efficient way to achieve the same thing ?
P.S : - I do not wish to use any libraries.
The only thing I see you miss is to set the initial value of counter variable.
I would write:
var counter = 1000; // or any useful value
setInterval(function() {
--counter;
}, 24 * 60 * 60 * 1000); // this is more self-explanatory than 86400000, and, being evaluated just once, it will have a tiny effect on the performace of the script
I don't see any problem in the way you write it. You use interval, ok, but this is not the worst evil you may do to set up the variable value.
You may think of another solution with a function which returns you the current counter.
var initialValue = 20000;
function getCounter() {
return initialValue - Math.floor(Date.now() / 1000 / 60 / 60 / 24);
}
console.log(getCounter());
The difference is that it takes the current day number starting from the UNIX time beginning. Every day the day number will be increased, so the result of the function will be decreased by 1.
But still I don't see how this solution can be better than yours.
I'm not totally sure why, but using setInterval like this makes me uncomfortable.
If I were to require this, I would use something like this approach:
var counter = 10;
var timeout = new Date();
setInterval(function(){
if(new Date() >= timeout)
{
--counter; // the action to perform
timeout = new Date(timeout.getTime() + 86400000); // update the timeout to the next time you want the action performed
}
console.log(counter);
},1000); // every second is probably way more frequent than necessary for this scenario but I think is a decent default in general
One thing that this allows is to, for example, set the next timeout to midnight of tomorrow rather than being locked in to "X seconds since the previous execution". The key is the inversion of control - the action itself can now dictate when it should next run.
Though I would probably abstract away the details behind an interface accepting a start, interval, and action.
The biggest problem in my eyes is that you have to keep this one JS process running consistently for days at a time to have it do what you need. The world is not so perfect that things don't need an occasional reboot...including the average JS process.
Personally I would store a timestamp of my starting point, then (whenever I need to know how much time has elapsed) grab a new timestamp and use it to calculate how many days it has been. That way even if something interrupts my process I can still be right where I started.
Maybe use window.localStorage to save the last time, and if it is greater than 60*60*24 (seconds in a day) set the last time to this morning/now/1:00 and then decrease the value and save it.
Example:
var d = new Date();
var mins = -(1+d.getHours())*60+d.getMinutes();
var secs = mins*60+d.getSeconds(); // total seconds passed today from 1:00
var now = d.getCurrentTime():
var lastCheck = localStorage.getItem("lastCheck");
if (!lastCheck)
{
localStorage.saveItem("lastCheck",now-secs); // beginning of today
}
var dayPassed = now - lastCheck > 24*60*60; // change to see if a day has passed
if (dayPassed)
{
// save seconds
localStorage.setItem("counter",localStorage.getItem("counter")-1);
localStorage.saveItem("lastCheck",now-secs); // beginning of today
}
It makes more sense to me to check how many days have passed since a specific date and decrement that number of days from the counter. Mostly just because I wouldn't expect anybody to leave the same page open without the need or want to reload for days on end. I would do something like this:
counter = 365; // original counter
var start = new Date(2016, 03, 20); // original date
var now = new Date();
var days = Math.floor(Math.abs(start.getTime()-now.getTime())/(24*60*60*1000))
counter -= days;
That way every time you visited the page, it would be decremented correctly. Note that this ignores any issues with leap days or time zones. The example above would have a counter of 360 for me. And then if you did expect it to be open for days, reload it automatically with:
self.setTimeout(function(){document.location.reload()}, 86400000);
Through my searching I've found the following simple logic works really well for ensuring two time ranges don't overlap.
(StartA <= EndB) and (EndA >= StartB)
But it starts to get cumbersome (with my current approach) if I need to use many time ranges. Lots of If statements and such.
So what is a better approach? I've thought about filling arrays corresponding to 15 minute intervals of 'time' and running a check if those array places are 'occupied' when a new time range is added.
This will be using javascript, so I don't know if arrays are the most appropriate/lightweight however.
A straightforward way would be to store the dates in an array, sort it by start date, then check if the dates overlap. Here's a sample:
var dates = [];
var addDate = function(start, end){
dates.push({start: start, end: end});
};
var datesOverlap = function(){
var i;
dates.sort(function(a, b){ return a.start-b.start;});
for(i=0; i<dates.length-1; i++){
if(dates[i].end >= dates[i+1].start){
return true; // dates overlap
}
}
return false; // no dates overlap
};
Add all the dates you want to check with the addDate function. Then you'd call the datesOverlap function to check if any dates overlap.
I was just wondering if it is possible to have a javascript for loop that only iterates through the loop once a day i.e. when the date changes?
for(i=0; i < myArray.length; i++){
alert(myArray[i]);
}
So in the above loop, let it run, and freeze it or something only till the data changes, and the do another iteration, and just keep on doing that.. You know what I mean.
Thanks in advance!
Using localStorage is the best way to go when you don't have a server (because a user can change the computer's time and break your logic, and using a server it's harder to hack this)
Method below is more bulletproof:
// checks if one day has passed.
function hasOneDayPassed()
// get today's date. eg: "7/37/2007"
var date = new Date().toLocaleDateString();
// if there's a date in localstorage and it's equal to the above:
// inferring a day has yet to pass since both dates are equal.
if( localStorage.yourapp_date == date )
return false;
// this portion of logic occurs when a day has passed
localStorage.yourapp_date = date;
return true;
}
// some function which should run once a day
function runOncePerDay(){
if( !hasOneDayPassed() ) return false;
// your code below
alert('Good morning!');
}
runOncePerDay(); // run the code
runOncePerDay(); // does not run the code
If you want something to happen at predefined intervals, you can set a timeout/interval:
http://www.w3schools.com/js/js_timing.asp
For example:
var dayInMilliseconds = 1000 * 60 * 60 * 24;
setInterval(function() { alert("foo"); },dayInMilliseconds );
edit: since you mentioned that the code will be running in a browser, this assumes the browser is running for at least 24 hrs and will not work otherwise.
the best way to achieve it is by creating a cookie that lasts for1 day..
Even if after the refresh of the web page or browser gets closed that countdown will still continue..
setcookie($cookie_name, $cookie_value, time() + 86400, "/");
This means 86400 = 1 day
Hope it helps