JavaScript - find date of next time change (Standard or Daylight) - javascript

Here's a timely question. The rules in North America* for time change are:
the first Sunday in November, offset changes to Standard (-1 hour)
the second Sunday in March, offset changes to Daylight (your normal offset from GMT)
Consider a function in JavaScript that takes in a Date parameter, and should determine whether the argument is Standard or Daylight Saving.
The root of the question is:
how would you construct the date of the next time change?
The algorithm/pseudocode currently looks like this:
if argDate == "March"
{
var firstOfMonth = new Date();
firstOfMonth.setFullYear(year,3,1);
//the day of week (0=Sunday, 6 = Saturday)
var firstOfMonthDayOfWeek = firstOfMonth.getDay();
var firstSunday;
if (firstOfMonthDayOfWeek != 0) //Sunday!
{
//need to find a way to determine which date is the second Sunday
}
}
The constraint here is to use the standard JavaScript function, and not scrape any JavaScript engine's parsing of the Date object. This code won't be running in a browser, so those nice solutions wouldn't apply.
**not all places/regions in North America change times.*

if argDate == "March"
{
var firstOfMonth = new Date();
firstOfMonth.setFullYear(year,3,1);
//the day of week (0=Sunday, 6 = Saturday)
var firstOfMonthDayOfWeek = firstOfMonth.getDay();
var daysUntilFirstSunday = (7-firstOfMonthDayOfWeek) % 7;
var firstSunday = firstOfMonth.getDate() + daysUntilFirstSunday;
// first Sunday now holds the desired day of the month
}

1) I expect there could be some other rules in different countries. Some don't have daylight saving at all. So to find the answer in a specific locale you could probably loop throught 365(6) days to find the days where getTimetoneOffset() changes it's value. This should not be a big lag in performance.
2) Then, you can get the specific hour when the time is changes (2 am for US?). Suggest another loop throught 24 hours
PS: Ok, someone has already done the job =). You should test it before using (because I didn't test)
PPS: Your first question was "is daylight applied or not for specific date?". This answer solves the task

Related

Incorrect time while copy pasting a date time value using google apps script [duplicate]

I'm trying to get from a time formatted Cell (hh:mm:ss) the hour value, the values can be bigger 24:00:00 for example 20000:00:00 should give 20000:
Table:
if your read the Value of E1:
var total = sheet.getRange("E1").getValue();
Logger.log(total);
The result is:
Sat Apr 12 07:09:21 GMT+00:09 1902
Now I've tried to convert it to a Date object and get the Unix time stamp of it:
var date = new Date(total);
var milsec = date.getTime();
Logger.log(Utilities.formatString("%11.6f",milsec));
var hours = milsec / 1000 / 60 / 60;
Logger.log(hours)
1374127872020.000000
381702.1866722222
The question is how to get the correct value of 20000 ?
Expanding on what Serge did, I wrote some functions that should be a bit easier to read and take into account timezone differences between the spreadsheet and the script.
function getValueAsSeconds(range) {
var value = range.getValue();
// Get the date value in the spreadsheet's timezone.
var spreadsheetTimezone = range.getSheet().getParent().getSpreadsheetTimeZone();
var dateString = Utilities.formatDate(value, spreadsheetTimezone,
'EEE, d MMM yyyy HH:mm:ss');
var date = new Date(dateString);
// Initialize the date of the epoch.
var epoch = new Date('Dec 30, 1899 00:00:00');
// Calculate the number of milliseconds between the epoch and the value.
var diff = date.getTime() - epoch.getTime();
// Convert the milliseconds to seconds and return.
return Math.round(diff / 1000);
}
function getValueAsMinutes(range) {
return getValueAsSeconds(range) / 60;
}
function getValueAsHours(range) {
return getValueAsMinutes(range) / 60;
}
You can use these functions like so:
var range = SpreadsheetApp.getActiveSheet().getRange('A1');
Logger.log(getValueAsHours(range));
Needless to say, this is a lot of work to get the number of hours from a range. Please star Issue 402 which is a feature request to have the ability to get the literal string value from a cell.
There are two new functions getDisplayValue() and getDisplayValues() that returns the datetime or anything exactly the way it looks to you on a Spreadsheet. Check out the documentation here
The value you see (Sat Apr 12 07:09:21 GMT+00:09 1902) is the equivalent date in Javascript standard time that is 20000 hours later than ref date.
you should simply remove the spreadsheet reference value from your result to get what you want.
This code does the trick :
function getHours(){
var sh = SpreadsheetApp.getActiveSpreadsheet();
var cellValue = sh.getRange('E1').getValue();
var eqDate = new Date(cellValue);// this is the date object corresponding to your cell value in JS standard
Logger.log('Cell Date in JS format '+eqDate)
Logger.log('ref date in JS '+new Date(0,0,0,0,0,0));
var testOnZero = eqDate.getTime();Logger.log('Use this with a cell value = 0 to check the value to use in the next line of code '+testOnZero);
var hours = (eqDate.getTime()+ 2.2091616E12 )/3600000 ; // getTime retrieves the value in milliseconds, 2.2091616E12 is the difference between javascript ref and spreadsheet ref.
Logger.log('Value in hours with offset correction : '+hours); // show result in hours (obtained by dividing by 3600000)
}
note : this code gets only hours , if your going to have minutes and/or seconds then it should be developped to handle that too... let us know if you need it.
EDIT : a word of explanation...
Spreadsheets use a reference date of 12/30/1899 while Javascript is using 01/01/1970, that means there is a difference of 25568 days between both references. All this assuming we use the same time zone in both systems. When we convert a date value in a spreadsheet to a javascript date object the GAS engine automatically adds the difference to keep consistency between dates.
In this case we don't want to know the real date of something but rather an absolute hours value, ie a "duration", so we need to remove the 25568 day offset. This is done using the getTime() method that returns milliseconds counted from the JS reference date, the only thing we have to know is the value in milliseconds of the spreadsheet reference date and substract this value from the actual date object. Then a bit of maths to get hours instead of milliseconds and we're done.
I know this seems a bit complicated and I'm not sure my attempt to explain will really clarify the question but it's always worth trying isn't it ?
Anyway the result is what we needed as long as (as stated in the comments) one adjust the offset value according to the time zone settings of the spreadsheet. It would of course be possible to let the script handle that automatically but it would have make the script more complex, not sure it's really necessary.
For simple spreadsheets you may be able to change your spreadsheet timezone to GMT without daylight saving and use this short conversion function:
function durationToSeconds(value) {
var timezoneName = SpreadsheetApp.getActive().getSpreadsheetTimeZone();
if (timezoneName != "Etc/GMT") {
throw new Error("Timezone must be GMT to handle time durations, found " + timezoneName);
}
return (Number(value) + 2209161600000) / 1000;
}
Eric Koleda's answer is in many ways more general. I wrote this while trying to understand how it handles the corner cases with the spreadsheet timezone, browser timezone and the timezone changes in 1900 in Alaska and Stockholm.
Make a cell somewhere with a duration value of "00:00:00". This cell will be used as a reference. Could be a hidden cell, or a cell in a different sheet with config values. E.g. as below:
then write a function with two parameters - 1) value you want to process, and 2) reference value of "00:00:00". E.g.:
function gethours(val, ref) {
let dv = new Date(val)
let dr = new Date(ref)
return (dv.getTime() - dr.getTime())/(1000*60*60)
}
Since whatever Sheets are doing with the Duration type is exactly the same for both, we can now convert them to Dates and subtract, which gives correct value. In the code example above I used .getTime() which gives number of milliseconds since Jan 1, 1970, ... .
If we tried to compute what is exactly happening to the value, and make corrections, code gets too complicated.
One caveat: if the number of hours is very large say 200,000:00:00 there is substantial fractional value showing up since days/years are not exactly 24hrs/365days (? speculating here). Specifically, 200000:00:00 gives 200,000.16 as a result.

function reminding me about a date

I have a function and it represent a date that is 2 weeks off from start date, counted by each passing Thursday, but excludes the Thursday of the week the date was made.
function GetThursdayIn2Weeks(date)
{
var day = date.getDay();
// Add 2 weeks.
var newDate = new Date(date.setTime(date.getTime() + (14 * 86400000)));
// Adjust for Thursday.
var adjust = 4 - day;
if (adjust <= 0) // Might need to be changed - See comments!
adjust +=7;
// Apply Thursday adjustment.
newDate = new Date(newDate.setTime(newDate.getTime() + (adjust * 86400000)));
return newDate;
}
How would I make this set off a different function every day that passed, starting a week after the beginning of the process, remind me about the due date coming up, but before the end of the date of the process?
You can use setTimeout() to execute a reminder after a set time. However, the problem is that your javascript environment will probably not keep running for such long times, be it node.js or your browser.
I would suggest those mechanisms :
store your target date in localstorage after calculating it with your given code
define a function that will use setTimeout() to define the next occurrence of the reminder for a given target date
when the page is loaded, use that function for each date stored in the localstorage
when a date is added to the localstorage, or a given target date reachs one of its reminders, call the function for this specific date
The mentioned function should set a timer for the first day that is at the same time greater than the current date, greater than the day 1 week before the target date, and lower than the target date.
Here is an 'hopefully) working JSFiddle.

Why is this PDF javascript Date being incorrectly calculated only once a year?

I have an interesting result from the javascript in an Acrobat PDF Form
I have a series of date form fields. The first field is for user entry and the remaining fields are calculated by javascript, each field incremented by one day.
The code is:
var strStart = this.getField("userField").value;
if(strStart.length > 0) {
var dateStart = util.scand("dd/mm/yy",strStart);
var dateStartMilli = dateStart.getTime();
var oneDay = 24 * 60 * 60 * 1000 * 1; // number of milliseconds in one day
var dateMilli = dateStartMilli + oneDay;
var date = new Date(dateMilli);
event.value = util.printd("dd/mm/yy",date);
} else { event.value = "" }
The issue is if I input 05/04/15 in to the user field the result is 05/04/15 (same, wrong) while any other date of the year correctly increments by one day (ie 25/10/15 gives 26/10/15, 14/2/15 gives 15/2/15 etc)
The same error occurs on the 3rd of April 2016, 2nd of April 2017, etc (ie each year)
I have a fortnight (14) of these incrementing fields, each incrementing the date from the previous calculated field with the same javascript as above ("userField" is changed to date2, date3, date4 etc). What is very strange is that the next field that increments off the second of the two 05/04/15 correctly returns 06/04/15 and there isn't an issue after that.
Does anyone know why this might be?!
That doesn't happen on my browser's JavaScript engine and/or in my locale, so it must be an Acrobat thing or that date may be special in your locale (e.g., DST).
In any case, that's not the correct way to add one day to a JavaScript date, not least because some days have more than that many milliseconds and some have less (transitioning to and from DST).
The correct way is to use getDate and setDate:
var strStart = this.getField("userField").value;
if(strStart.length > 0) {
var dateStart = util.scand("dd/mm/yy",strStart);
dateStart.setDate(dateStart.getDate() + 1); // Add one day
event.value = util.printd("dd/mm/yy",dateStart);
} else { event.value = "" }
setDate is smart enough to handle it if you go past the end of the month (per specification).
If it's DST-related, the above will fix it. If it's some weird Acrobat thing, perhaps it will work around it. Either way, it's how this should be done.
Let me guess, that's the day daylight savings starts in your locale? 24 hours after midnight is not always the next day, because some days have 25 hours.
Approaches that come to my head:
manipulate the day. (This is easy if Acrobat allows dates like the 32nd of January, because oyu can just increment the day. Otherwise, maybe don't bother because leap years aren't much better than DST.)
don't start from midnight. If you never use the hour and minute within the day, don't pin your day at the strike of midnight, but at, say, 3am. After a change in DST status, later days in your fortnight might register as 2am or 4am, but as long as you're only using the day…

Calculate difference between 2 dates considering Daylight Saving Time

Given a start date, and a number of days, I need to display the end date = start date + number of days.
So I did something like this:
var endDate=new Date(startDate.getTime()+ONE_DAY);
Everything works fine, except that for 25 and 26 October gives one day less.
Ex.:
2014-01-01 + 2 days = 2014-01-03
2014-10-25 + 2 days = 2014-10-26 (here is the case I need to treat).
This difference appear because of the clock going back 1 hour. Practically 2014-10-27 00:00:00 becomes 2014-10-26 23:00:00.
A simple solution would be to compute this at another hour (example 3 AM). But I want to just display a note when this happens.
For example, if user inputs 2014-10-25, I show a popup saying [something].
Now here is the real problem... I can't seem to find any algorithm that says when clocks goes back in year X.
Example... in 2014 the day is 26 October. In 2016 is 30 October (https://www.gov.uk/when-do-the-clocks-change). Why? This date looks random to be, but I don't think it is. So... when does clock go back/forward?
EDIT: All answers/comments are helpful related to how to fix the problem. But... I already passed that stage. Now I only have an itch about "how on earth are the days when clock is changed computed?".
To find the difference between two dates in whole days, create Date objects, subtract one from the other, then divide by the milliseconds in one day and round. The remainder will only be out by 1 hour for daylight saving so will round to the right value.
You may also need a small function to convert strings to Dates:
// Return Date given ISO date as yyyy-mm-dd
function parseISODate(ds) {
var d = ds.split(/\D/);
return new Date(d[0], --d[1], d[2]);
}
Get the difference in days:
function dateDiff(d0, d1) {
return Math.round((d1 - d0)/8.64e7);
}
// 297
console.log(dateDiff(parseISODate('2014-01-01'), parseISODate('2014-10-25')));
If you want to add days to a date, do something like:
// Add 2 days to 2014-10-25
var d = new Date(2014, 9, 25);
d.setDate(d.getDate() + 2);
console.log(d); // 2014-10-27
The built–in Date object takes account of daylight saving (thought there are bugs in some browsers).
I prefer adding days this way:
var startDate = //someDate;
var endDate = new Date(startDate.getFullYear(),
startDate.getMonth(),
startDate.getDate()+1);
This way you don't have to worry about the days in the calendar.
This code add 1 day, if you want to add more, change the startDate.getDate()+1 for startDate.getDate()+NUMBER_OF_DAYS it works fine even if you are on the last day of month i.e. October 31th.
But maybe you can use #RobG solution which is more elegant than mine

why javascript's .getTime() + 24*60*60*1000 get's stack after 27 Oct 2013?

I was just creating a simple calendar when users clicks next it gets the following day, very simple code:
var dateSelected = new Date('02/06/2013'); //any date
var day = new Date(dateSelected.getTime() + 24*60*60*1000);
alert(day.getDate());
that works great for all dates but for some reason it doesn't get the next day when the date is 27 Oct 2013
var dateSelected = new Date('10/27/2013');
I don't seem to be able to figure out why, if I go manually to the next day 28 Oct it keeps working fine.
Any ideas why this happens?
UPDATE:
I fixed it by adding the time as well as the date:
var dateSelected = new Date('10/27/2013 12:00:00');
I strongly suspect this is because of your time zone - which we don't know, unfortunately.
On October 27th 2013, many time zones "fall back" an hour - which means the day is effectively 25 hours long. Thus, adding 24 hours to your original value doesn't change day if it started within the first hour of the day.
Fundamentally, you need to work out whether you're actually trying to add a day or add 24 hours - they're not the same thing. You also need to work out which time zone you're interested in. Unfortunately I don't know much about Javascripts date/time API, but this is almost certainly the cause of the problem.
Rather than adding the number of milliseconds in a day, you can use the set date function directly.
var dateSelected = new Date('10/27/2013');
var daysToAdd = 1;
var nextDay = new Date(dateSelected.getTime());
nextDay.setDate(dateSelected.getDate() + daysToAdd);
This also works when rolling over to the next month, and should work well with different time zones.
As Jon Skeet already mentioned, the problem results from your local timezone. As a possible solution, you can use the setDate and getDate functions of the Date object:
var dateSelected = new Date('02/06/2013'); //any date
dateSelected.setDate(dateSelected.getDate() + 1);
alert(dateSelected.getDate());
And of course, no JavaScript Date question could be complete without a Moment.js answer:
var m = moment('10/27/2013','MM/DD/YYYY').add('days', 1);
Superior API every time. :-)

Categories