Get duration (in Hours and minutes) between two Date objects - JavaScript - javascript

Although, I've seen multiple similar questions here on SO but none of them could help me to figure out what's wrong with my calculation. I know I can use library such as Moment.JS to simplify my solution but I want native JavaScript only solution.
I'm trying to calculate duration (in hours and minutes) between two Date objects but I'm getting negative (incorrect) duration.
function padNumber(number, width = 2, padWith = '0') {
const strNum = number.toString();
return strNum.length >= width ? strNum : new Array(width - strNum.length + 1).join(padWith) + strNum;
}
// Get UTC date time from PHP date (Y-m-d) and time (H:i:s) strings
function getUTCDateTime(date, time, timezoneOffset = -480) {
const dateParts = date.split('-').map((el) => Number(el)); // Y-m-d
const timeParts = time.split(':').map((el) => Number(el)); // H:i:s
const dateTimeUTC = new Date(Date.UTC(dateParts[0], dateParts[1], dateParts[2], timeParts[0], timeParts[1], timeParts[2]));
// Set back Singapore specific time (GMT+8:00)
dateTimeUTC.setUTCHours(dateTimeUTC.getUTCHours() + timezoneOffset / 60);
return dateTimeUTC;
}
function getDuration(timeStart, timeEnd = new Date()) {
const msDiff = timeEnd.getTime() - timeStart.getTime();
const minDiff = msDiff / 60000;
const hourDiff = Math.floor(msDiff / 3600000);
return {
hours: this.padNumber(hourDiff, 2),
minutes: this.padNumber(Math.floor(minDiff - 60 * hourDiff), 2)
};
}
// Got from server (in Singapore timezone)
const serverDate = '2018-10-18';
const serverTime = '00:22:51';
// Convert server date and time (timezone specific) strings to Date object
const serverUTC = getUTCDateTime(serverDate, serverTime);
// Get duration between server time and now
const duration = getDuration(serverUTC);
// Expected positive value but getting negative as server time is in past
console.log(duration);
I expected positive value in console log but I'm getting negative. Have I missed anything?

The problem stems from the fact that months are zero-based in JavaScript (i.e. January is 0, February is 1, and so on). Your date construction in getUTCDateTime() doesn't take this into account.
This line:
const dateTimeUTC = new Date(Date.UTC(dateParts[0], dateParts[1], dateParts[2], timeParts[0], timeParts[1], timeParts[2]));
Should be:
const dateTimeUTC = new Date(Date.UTC(dateParts[0], dateParts[1] - 1, dateParts[2], timeParts[0], timeParts[1], timeParts[2]));
Complete snippet:
function padNumber(number, width = 2, padWith = '0') {
const strNum = number.toString();
return strNum.length >= width ? strNum : new Array(width - strNum.length + 1).join(padWith) + strNum;
}
// Get UTC date time from PHP date (Y-m-d) and time (H:i:s) strings
function getUTCDateTime(date, time, timezoneOffset = -480) {
const dateParts = date.split('-').map((el) => Number(el)); // Y-m-d
const timeParts = time.split(':').map((el) => Number(el)); // H:i:s
const dateTimeUTC = new Date(Date.UTC(dateParts[0], dateParts[1] - 1, dateParts[2], timeParts[0], timeParts[1], timeParts[2]));
// Set back Singapore specific time (GMT+8:00)
dateTimeUTC.setUTCHours(dateTimeUTC.getUTCHours() + timezoneOffset / 60);
return dateTimeUTC;
}
function getDuration(timeStart, timeEnd = new Date()) {
const msDiff = timeEnd.getTime() - timeStart.getTime();
const minDiff = msDiff / 60000;
const hourDiff = Math.floor(msDiff / 3600000);
return {
hours: this.padNumber(hourDiff, 2),
minutes: this.padNumber(Math.floor(minDiff - 60 * hourDiff), 2)
};
}
// Got from server (in Singapore timezone)
const serverDate = '2018-10-18';
const serverTime = '00:22:51';
// Convert server date and time (timezone specific) strings to Date object
const serverUTC = getUTCDateTime(serverDate, serverTime);
// Get duration between server time and now
const duration = getDuration(serverUTC);
// Expected positive value but getting negative as server time is in past
console.log(duration);

Related

Google App Scripts - Getting a constant "Yesterday"

I'm trying to set a const "Yesterday" but the script is not recognised as a function (I'm using Google App Scripts).
I've tried different syntax including:
const yesterday = today.setDate(-1);
and
const yesterday = new Date(today.setDate(-1));
But neither worked.
I'm pretty sure it should be a minor change but I cannot figure out how to solve this.
A little help would be highly appreciated, thanks !
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('My Report');
function insertColumn() {
const range = sheet.getRange('H1:H69').getValues();
const newrange = sheet.getRange('I1:I69');
const rules = sheet.getConditionalFormatRules();
const today = Utilities.formatDate(new Date(), "GMT+7", "MM/dd/yyyy");
const yesterday = new Date(today.setDate(-1));
Your issue is that today is not a Date object, because you've called Utilities.formatDate on it. This is why you are getting an error when trying to use today.setDate(). So you need to use another variable to allow you to compute yesterday. For example:
const tmp = new Date();
const yesterday = new Date(tmp.setDate(tmp.getDate()-1))
Also note that setDate(-1) sets the date to the penultimate day in the previous month (e.g. March 30 when you are in April), you need to get the current date (using getDate) and subtract 1 from that to get yesterday's date.
getYesterday() {
let date = new Date();
let yesterday_milliseconds = date.getTime() - 1000 * 60 * 60 * 24;
let yesterday = new Date();
yesterday.setTime(yesterday_milliseconds);
let strYear = yesterday.getFullYear();
let strDay = yesterday.getDate();
let strMonth = yesterday.getMonth() + 1;
if (strMonth < 10) {
strMonth = "0" + strMonth;
}
if (strDay < 10) {
strDay = "0" + strDay;
}
return strYear + "-" + strMonth + "-" + strDay;
},
function yesterday() {
let dt = new Date();
Logger.log(new Date(dt.setDate(dt.getDate() - 1)));
}
From MDN setDate() returns: The number of milliseconds between 1 January 1970 00:00:00 UTC and the given date... not a date object

Javascript time zone calculation issues

I am trying to make a countdown timer for ticket response expiry. My code will successfully calculate the time left when dealing with hours bar the extra hour (+0100) GMT.
I have tried dealing with this in all manner of ways suggested on here to no avail. Any suggestions? Should I give in & learn Luxon?
The converttoUTC function seen is not called as it has not worked & only messed up the calculation further.
The dates that's been pulled from the table is in the following format 2022-04-15 17:47:19
The Time Limits being pulled from the table are in the following format, "15 mins" "6 hours".
<!--===========================================================================================-->
<script>
function func(creationDatePlusLimit) {
// var dateValue= document.getElementById("date").value;
var date = Math.abs((new Date().getTime() / 1000).toFixed(0));
var date2 = Math.abs((new Date(createdOnDate).getTime() / 1000).toFixed(0));
var diff = date2 - date;
var days = Math.floor(diff / 86400);
var hours = Math.floor(diff / 3600) % 24;
var mins = Math.floor(diff / 60) % 60;
var secs = diff % 60;
// document.getElementById("data").innerHTML = days + " days, " + hours + ":" + mins + ":" + secs;
if (days>=0) {
return days + " days, " + hours + ":" + mins + ":" + secs;
} else {
return "late";
}
}
const loopThroughTableRows = () => {
const tableRows = Array.from(document.getElementsByTagName('tr'));
tableRows.shift(); // removes first one, header
tableRows.forEach(row => {
var rowCols = row.getElementsByTagName('td');
var createdOnDate = rowCols[3];
var timeLimit = rowCols[7];
function convertDateToUTC(date) {
return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
}
const createdDate = new Date(createdOnDate.innerText);
// if time limit is in days, remove text, & add to creation date-------------------//
var limitdays = timeLimit.innerText;
if (limitdays.includes(" days")) {
limitdays = limitdays.replace("days", "");
limitdays= parseInt(limitdays);
function addDaysToDate(createddate, days) {
var result = createddate;
result.setDate(createddate.getDate()+days);
return result;
}
var newDate = addDaysToDate(createdDate, limitdays);
// format newdate to iso & remove unwanted characters
newDate = newDate.toISOString();
if (newDate.includes("T")) {
newDate = newDate.replace("T", " ");
}
if (newDate.includes(".000Z")) {
newDate = newDate.replace(".000Z", "");
}
};
//===================================================================================//
// if time limit is in hours, remove text, & add to creation date-------------------//
// var limithours = timeLimit.innerText;
// if (limithours.includes(" hours")) {
// limithours = limithours.replace("hours", "");
// limithours= parseInt(limithours);
//
// function addHoursToDate(createddate, hours) {
// var result = createddate;
// // result.setHours(createddate.getDate()+6);
// return result;
// }
// var newDate = addHoursToDate(createdDate, limithours);
//
// // format newdate to iso & remove unwanted characters
// newDate = newDate.toISOString();
// if (newDate.includes("T")) {
// newDate = newDate.replace("T", " ");
// }
// if (newDate.includes(".000Z")) {
// newDate = newDate.replace(".000Z", "");
// }
// };
//===================================================================================//
const testRow = rowCols[8];
const timeDifference = func(newDate);
testRow.innerText = newDate;
});
}
loopThroughTableRows();
setInterval(loopThroughTableRows, 1000)
</script>
Given you have a creation date like "2022-04-15 17:47:19" and duration to expiry in the format "n [days|hours|minutes]", you probably want to do everything as local to avoid timezone issues. If you use Date objects, then it's simple represent it as a UTC timestamp using toISOString.
Consider the following, which returns a Date object for the expiry based on the creationDate and time limit. It does everything as local, so timezone and daylight saving issues are handled by the Date object. There's no validation of input, so that should be added.
// Parse YYYY-MM-DD HH:mm:ss as local
function parseISOLocal (ts) {
let [Y, M, D, H, m, s] = ts.split(/\D/);
return new Date(Y, M-1, D, H, m, s);
}
// Format a Date as YYYY-MM-DD HH:mm:ss
function formatISOLocal(date = new Date()) {
return date.toLocaleString('en-CA',{hour12: false}).replace(',','');
}
// Parse limit in "value [days|hours|minutes]" to {value, unit}
// e.g. "3 days" to {value: 3, unit:'day'}
function normaliseLimit(limit) {
let [value, unit] = limit.toLowerCase().split(/\s+/);
return {value: +value, unit: {d:'day', h:'hour', m:'minute'}[unit[0]]};
}
// Given:
// createdAt in YYYY-MM-DD HH:mm:ss format and
// limit as "number [days|hours|minutes]" return a Date for expiry
// I.e. createdAt plus limit
function getExpiryDate(createdAt, limitString) {
let expiry = parseISOLocal(createdAt);
let limit = normaliseLimit(limitString);
let method = {day:'Date', hour:'Hours', minute:'Minutes'}[limit.unit];
return new Date(expiry[`set${method}`](expiry[`get${method}`]() + limit.value));
}
let createdAt = formatISOLocal();
[{createdAt: createdAt, limit:'1 Day'},
{createdAt: createdAt, limit:'3 hours'},
{createdAt: createdAt, limit:'12 minutes'}
].forEach(({createdAt, limit}) => console.log(
`createdAt: ${createdAt}\n` +
`limit : ${limit}\n` +
`Expires : ${formatISOLocal(getExpiryDate(createdAt, limit))}`
));
Once you have the expiry date, you can work out the remaining time as days, hours, minutes, seconds as given at Difference between two dates in years, months, days in JavaScript and use it in a timer to show a count down, to work out which items have expired, etc.

Time difference between an ISO 8601 date and now

I have a comment section on my website and each message have its created_at date time. After fetching it from the MariaDB database, I get a string like "2021-06-15T12:45:28.000Z" (ISO 8601). Then, I convert it to a "x minutes ago" text instead of the full date.
But then, I'm having some trouble when the date is parsed.
const epochs = [
["année", 31536000],
["mois", 2592000],
["jour", 86400],
["heure", 3600],
["minute", 60],
["seconde", 1]
];
function getInterval(timeAgo) {
for (let [ name, seconds ] of epochs) {
const interval = Math.floor(timeAgo / seconds);
if (interval >= 1) {
return { interval: interval, epoch: name };
}
}
}
function dateToString(d) {
const date = new Date(d);
console.log("Created at:", date);
console.log("Now:", new Date());
const timeAgo = Math.floor((new Date() - date) / 1000);
console.log("Time ago:", timeAgo);
const { interval, epoch } = getInterval(timeAgo);
const plural = (interval === 1 || epoch.slice(-1) === "s") ? "" : "s";
console.log("----------------------------");
return `Il y a ${interval} ${epoch}${plural}`;
}
dateToString("2021-06-15 12:45:28"); // Works fine
dateToString("2021-06-15T12:45:28.000Z"); // The "timeAgo" is negative
Subtracting the ISO date gives a negative number. I'm pretty sure this is a timezone problem because the
minimal value of the substraction is almost -7200 which is two hour and I'm in a UTC+2 timezone.
Have you any idea how can I fix this?
Thanks
Try adding or subtracting the timezoneOffset of the local computer from the UTC you get when you pass Z
I fixed your plural too
const epochs = {
"années": 31536000,
"mois": 2592000,
"jours": 86400,
"heures": 3600,
"minutes": 60,
"secondes": 1
};
const singular = {
"années": "ans",
"mois": "mois",
"jours": "jour",
"heures": "heure",
"minutes": "minute",
"secondes": "seconde"
};
const getInterval = timeAgo => {
const epoch = Object.entries(epochs).filter(([key, val]) => timeAgo >= val).shift();
const obj = {
epoch: epoch[0],
interval: parseInt(timeAgo / epoch[1])
}
if (obj.interval === 1) obj.epoch = singular[obj.epoch]
return obj
};
const aMinute = 60 * 1000
function dateToString(d) {
const date = new Date(d);
if (d.includes("Z")) {
let tzo = date.getTimezoneOffset();
tzo = (tzo > 0 ? tzo * -1 : tzo);
tzo *= aMinute
date.setTime(date.getTime() + tzo)
}
console.log("Created at:", date.toLocaleString());
console.log("Now:", new Date());
const timeAgo = Math.floor((new Date() - date) / 1000);
console.log("Time ago:", timeAgo);
console.log(getInterval(timeAgo))
const { epoch, interval } = getInterval(timeAgo);
console.log(epoch)
console.log("----------------------------");
return `Il y a ${interval} ${epoch}`;
}
console.log(
dateToString("2021-06-15 12:45:28"), "\n",
dateToString("2021-06-15T12:45:28.000Z"), "\n",
dateToString("2019-06-15T12:45:28.000Z"), "\n"
)

Get timezone from date string in JavaScript

I'm working on a function that should check if a given dateString has the same timeZone as the browser timeZone. To get the browser timeZone I can use either a) Intl.DateTimeFormat().resolvedOptions().timeZone which will return Europe/Amsterdam for example or b) new Date().getTimeZoneOffset() that will return -60. Both are fine.
The tricky part is to get the timeZone from the dateString I want to pass, for example from: 2021-01-01T00:00:00-05:00 (which should be America/New_York or 300 AFAIK). How can I get the timeZone from that date? I tried to do: new Date('2021-01-01T00:00:00-05:00').getTimeZoneOffset() but that will convert it to the timeZone of my browser again, returning -60.
Example of function:
function isSameTimeZone(date) {
// function to get time zone here
const a = getTimeZone(date)
return a === new Date().getTimeZoneOffset() || Intl.DateTimeFormat().resolvedOptions().timeZone
}
Testcases
2021-01-01T00:00:00-08:00 (SF)
2021-01-01T00:00:00-05:00 (NY)
2021-01-01T00:00:00+05:30 (Mumbai)
2021-01-01T00:00:00+01:00 (Amsterdam)
Anyone out there with a solution? Thanks in advance!
Here's my method;
function checkTimezone(dateString) {
const testDate = new Date(dateString);
const dateRegex = /\d{4}-\d{2}-\d{2}/;
const myDate = new Date(testDate.toISOString().match(dateRegex)[0]);
return !(testDate - myDate);
}
const testCases = ['2021-01-01T00:00:00-05:00', '2021-01-01T00:00:00-08:00', '2021-01-01T00:00:00-05:00', '2021-01-01T00:00:00+05:30', '2021-01-01T00:00:00+01:00'];
testCases.forEach(testCase => console.log(checkTimezone(testCase)));
So here's how it works, you pass in your date string and create a new Date() instance with it.
Then I get the ISO string with the Date.ISOString() method and match it with the original string to get the date and create another Date instance from it without the time.
Then I find the difference (comes in milliseconds), and convert it to minutes
So I've been testing around a bit and in the end I came up with the following solution, based on the dates I provided in my question.
const getTimeZoneOffsetFromDate = date => {
/*
Check if the offset is positive or negative
e.g. +01:00 (Amsterdam) or -05:00 (New York)
*/
if (date.includes('+')) {
// Get the timezone hours
const timezone = date.split('+')[1]
// Get the hours
const hours = timezone.split(':')[0]
// Get the minutes (e.g. Mumbai has +05:30)
const minutes = timezone.split(':')[1]
/*
Amsterdam:
const offset = 01 * -60 = -60 + -0 = -60
*/
const offset = hours * -60 + parseInt(-minutes)
return offset === new Date().getTimezoneOffset()
}
// Repeat
const timezone = date.slice(date.length - 5)
const hours = timezone.split(':')[0]
const minutes = timezone.split(':')[1]
/*
New York:
const offset = 05 * 60 = 300 + 0 = 300
*/
const offset = hours * 60 + parseInt(minutes)
return offset === new Date().getTimezoneOffset()
}
console.log(getTimeZoneOffsetFromDate('2021-01-01T00:00:00+01:00'))

How do I convert this date from momentjs to plain JavaScript

I have a timestamp that I am trying to roundto the nearest UTC Monday 00:00:00:00:000Z
My code in moment looks like this
let now = Date.now()
moment.unix(now / 1000).utc().startOf("isoWeek").valueOf()
I am trying to do this in plain JS without moment and I am not getting the same answer
const nearestMonday = date => {
const monday = 1;
const currentDay = date.getDay();
const distance = (monday + 7 - currentDay) % 7;
const newDate = new Date(date.getTime());
newDate.setDate(date.getDate() + distance);
newDate.setHours(0, 0, 0, 0);
return newDate;
}
> d = Date.now()
1545989455067
> nearestMonday(new Date(d)).getTime()
1546194600000
> m.unix(Date.now() / 1000).utc().startOf("isoWeek").valueOf()
1545609600000
I am in GMT + 530 zone , what do I change to get the same answer as moment
Ok, so we have a few problems here:
First: Timezones
Date works with your local timezone, so when you do newDate.setHours(0, 0, 0, 0); and stuff like that, it sets the object to that hours in your timezone. When you do .getTime(), however, it does return millis from epoch in UTC.
The result of this being: if you are in gmt+530 (India, I believe) when you do a .getTime() the millis from epoch will be off by that difference (5h 30m).
To compensate that, you can use getTimezoneOffset():
const nearestMonday = date => {
const monday = 1;
const currentDay = date.getDay();
const distance = (monday + 7 - currentDay) % 7;
const newDate = new Date(date.getTime());
newDate.setDate(date.getDate() + distance);
newDate.setHours(0, 0, 0, 0);
newDate.setTime(newDate.getTime()-1000*60*newDate.getTimezoneOffset());
return newDate;
}
On the other hand, your code using moment will work properly with timezones, so there's no need to change it.
Second: What monday?
Your function nearestMonday calculates the next Monday.
The function startOf('isoWeek') sets the date to the Monday of the current week.
If you want both to calculate the current, you should modify your nearestMonday like:
const nearestMonday = date => {
const monday = 1;
const currentDay = date.getDay();
const distance = monday - currentDay;
console.log('dist', distance);
const newDate = new Date(date.getTime());
newDate.setDate(date.getDate() + distance);
newDate.setHours(0, 0, 0, 0);
newDate.setTime(newDate.getTime()-1000*60*newDate.getTimezoneOffset());
return newDate;
}
Last: Sundays?
getDay() on Sunday will return a 0. Therefore, the "nearestMonday" will be the day after that. I haven't corrected it since I don't know if that's the desired behaviour, but noting it just for completion sake
I think this may do what you want:
const nearestMonday = date => {
const day = 1000*60*60*24;
const week = day*7;
return new Date(Math.floor(date.getTime()/week)*week-3*day);
}

Categories

Resources