I am trying to setup a interval to run every morning at 0730. I am trying to use momentjs to accomplish this.
e.g:
current time - 0730 = how long to wait until it is time to run the function.
For some reason no matter how I manipulate the moment it seems to come back with some wonky math. For example it is currently 0616 in the morning and when I attempted to get this to do the math it came back with a 6hr difference not just over an hour.
var endOfShift = moment('0730', 'HHmm').format(),
now = moment().utcOffset(-8).format(),
diff = moment(now).local() - moment(endOfShift).local();
console.log(endOfShift); // => 2019-12-20T07:30:00+00:00
console.log(now); // => 2019-12-20T06:11:38-08:00
console.log(diff); // => 24098000 milliseconds = 6.6938888889hrs
I have tried removing the utcOffset from now which then the out put for the time/date incorrect. I have tried adding utcOffset to the endOfShift variable. No matter how I mess with the utc it still seems to = around 6hrs when it should be about 1ish. I have tried just removing the utcOffset from everything and just let it do its thing and it still gets the math wrong.
I have also tried moment's diff method w/ similar results.
What am I missing?
-Adam
I think you can achieve this with something like this
const now = moment();
let targetTime = moment('07:30', 'hh:mm'); // could be in past or in future
if (targetTime.isBefore(now)) {
targetTime = targetTime.add(1, 'day'); // add one day if waiting for tomorrows time
}
const diff = targetTime.diff(now);
console.log(diff);
const otherDiff = targetTime - now;
console.log(otherDiff);
The values for diff and otherDiff should be equal so it's up to you which one you prefer.
Related
I need to convert a complete hour by hour into %, but I am not sure if moment is able to do that.
Eg.:
I do have
let start = 08:00:00
// until
let end = 09:00:00
This is equal: 100%, but I receive an information from frontend value:
let data = 08:25:40
I need to know how much percent of hour have been passed from start, until the end.
I do have an workaround, but this is a little bit like a trick not a real solution.
Does anybody knows if JS has a library that can do this for me, easily?
Thanks.
Count the time difference from start to end and from start to your data in milisecond, then do simple math. Use diff method to calculate the time difference in milliseconds.
Here is a similar code snippet:
let start = 08:00:
let end = 09:00:00
let data = 08:25:40
const mStart = moment(start)
const mEnd = moment(end)
const mData = moment(data)
const percentile = 100.0 * mData.diff(mStart)/mEnd.diff(mStart)
Im trying to convert duration in seconds to human friendly output using moment js and combination of duration and format.
Im getting the duration in seconds and then formatting it like so:
const value = 337650
const duration = (durationSeconds) => {
const duration = moment.duration(durationSeconds, 'seconds')
return moment.utc(duration.asMilliseconds()).format('D [days] HH:mm:ss')
}
console.log(`Duration: ${duration(value)}`)
outputs
"Duration: 4 days 21:47:30"
JSBin here
The problem I have is that it seems wrong. Online services like https://www.tools4noobs.com/online_tools/seconds_to_hh_mm_ss/ shows one day less.
Im not sure what library/algorithm these services are using and if they are showing the correct time elapsed or moment js.
Any ideas greatly appreciated!
You seem to be converting the duration into an epoch time, then formatting it, which will be wrong.
Take a look at this Moment issue for details about duration formatting. https://github.com/moment/moment/issues/1048
Simple formatting can be done using the humanize method:
let value = 337650
let duration = moment.duration(value, 'seconds')
duration = duration.humanize()
console.log(duration)
"4 days"
Fore more sophisticated formatting you may want to dive into: https://github.com/jsmreese/moment-duration-format.
try something simple:
var x = 337650%86400;
var days = (337650 - x)/86400;
var y = x%3600;
var hours= (x-y)/3600;
var sec = y%60;
var mins=(y-sec)/60;
alert(days + ' days ' + hours+':'+mins+':'+sec)
You can use the moment-duration-format plugin to format durations, the previous approach (creating a new absolute date from a duration) is not going to work. When you add 337650 seconds to the Unix Epoch start, you get January 4, 1970, 21:47:30, which is why you're seeing the day as 4.
As a further example of how this will go badly wrong, imagine we need to format a duration of, say, 2700000 seconds. This will give us an output of "1 days 06:00:00". Why? Because it's equivalent to February 1 1970, 06:00. (The real result will be 31 days, 06:00:00).
I don't think we can blame the authors of moment.js (they've done an amazing job), it's just not using the format function on moment.js as it is supposed to be used.
By using the format function from moment-duration-format, we're formatting as a duration rather than as an absolute time and we get the right result!
momentDurationFormatSetup(moment);
const value = 337650
const duration = (durationSeconds) => {
const duration = moment.duration(durationSeconds, 'seconds')
return duration.format('D [days] HH:mm:ss');
}
console.log(`Duration: ${duration(value)}`)
document.getElementById('output').innerHTML = `Duration: ${duration(value)}`;
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-duration-format/2.2.2/moment-duration-format.min.js"></script>
<div id="output">
</div>
JSFiddle: https://jsfiddle.net/t07ez8o4/9/
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.
I've been working on calculating the total hours, minutes and seconds between two times using the moment.js library. The problem I have is that the calculations don't recognise that it's a day and return more hours than what is there. I'll give you an example:
I want to know how many hours are between 21:00 and 06:00, the answer is 9, however, the calculation brings back -15, this is also technically correct. I need to tell moment that it should use a day to calculate this value. I can get it to work if I use a DateTime picker but I don't want to use that as the user is only required to provide a time.
My application uses KendoUI for MVC and moment.js, moment.duration.format.js and moment.range.js
Here is my code which will return -15
#(Html.Kendo().TimePicker().Name("start").Value("21:00"))
#(Html.Kendo().TimePicker().Name("end").Value("06:00"))
<button class="btn btn-default btn-success" onclick="calc()">Plot</button>
Here is the javascript that works with this.
function calc() {
window['moment-range'].extendMoment(moment);
console.clear();
var dformat = "HH:mm:ss";
var start = $("#start").data("kendoTimePicker").value();
var end = $("#end").data("kendoTimePicker").value();
var startTime = moment(kendo.toString(start));
var endTime = moment(kendo.toString(end));
var duration = moment.duration(endTime.diff(startTime));
var hours = parseInt(duration.asHours());
console.log(hours);
}
If we change this to use DateTimePicker instead, it understands there is a day and calculates 9 hours. So how do I get around this? How can I achive this without using a datetime picker? Can I leaverage moment.js startof day or something?
Thanks to #VincenzoC I have managed to fix this problem. Here is the code which checks if the end time is before the start time and if it is, add a single day. This means the resulting time is accurate.
var startTime = moment(start);
var endTime = moment(end);
if (endTime.isBefore(startTime))
{
endTime.add(1, 'd');
}
//
//After the above condition has been passed, calculate the difference
var duration = moment.duration(endTime.diff(startTime));
//
//Any format you want
console.log(duration.format("HH"))
I am creating a countdown for an event and the server gives me the number of seconds left till this event. It works fine in the same time zone America/New_York but I am not sure how to achieve this for a different time zone. I guess I have to add/subtract a number of seconds based on user't time zone. I am taking into account that the number of seconds returned by the server will always be in EST. Can someone advise?
So far I have this but I'm getting an error:
let serverZoneTime = new moment().tz.zone("America/New_York").offset(now);
let currentZoneTime = moment.tz.guess().offset(now);
console.log((EstTzOffset - currentTz));
First of all, if this is an event at 6pm on a certain day I would get the exact timestamp or UTC time for that event start time. Below I'm using a fake timestamp.
This is important because the people viewing your event could change from EST to DST between "now" (which you are using above) and 6pm on the event day.
It sounds like you already have the countdown working but it is just the timezone issues you are dealing with so I'll skip the countdown logic.
const moment = require('moment-timezone');
// Get User's Timezone
const userTimezone = moment.tz.guess(); // this has to be on the client not server
const serverTimezone = "America/New_York";
// Get the exact timestamp of the event date apply the server timezone to it.
const serverEventTime = moment(34534534534, 'x').tz(serverTimezone);
// Take the server time and convert it to the users time
const userEventTime = serverEventTime.clone().tz(userTimezone);
// From here you can format the time however you want
const formattedUserEventTime = userEventTime.format('YYYY-MM-DD HH:mm:ss');
// Or format to a timestamp
const userEventTimestamp = userEventTime.format('x');
For a countdown you'll want the time now as well, which follows the same logic as above:
const serverTimeNow = moment().tz(serverTimezone);
const userTimeNow = serverTimeNow.clone().tz(userTimezone);
// Get timestamp so we can do easier math with the time
const userNowTimestamp = userTimeNow.format('x');
Now all we have to do is subtract now time from the event time to get the difference and repeat every second using a setInterval() perhaps.
const millisecondsToEvent = userEventTimestamp - userNowtimestamp;
const secondsToEvent = millisecondsToEvent / 1000;
Hopefully that's useful to someone (just realized that this was two years old).