Fallback countdown timer to be set to same day/time every week - javascript

If the user sets a date on the backend (via jQuery DateTimer Picker) the following acf_vars.timer variable would look like this on the frontend:
2021 2 9 13 08 00
I have the following construct as a countdown timer (CODEPEN):
const [y, month, d, h, minute, s] = acf_vars.timer.split(' ');
// monthIndex in Date Object begins with 0, so we subtract 1
const countDownDate = new Date(y, month - 1, d, h, minute, s).getTime();
const updateCountdown = () => {
const now = new Date().getTime(); // Get today's date and time
const distance = countDownDate - now; // Find distance between now and the countdown date
const expiredTimer = distance <= 0;
let days = Math.floor(distance / (1000 * 60 * 60 * 24));
let hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
let minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
let seconds = Math.floor((distance % (1000 * 60)) / 1000);
if (expiredTimer) {
days = hours = minutes = seconds = 0;
clearInterval(timerInterval);
}
document.querySelectorAll('.ticker').forEach((container) => {
container.children[0].classList.contains('days') &&
(container.children[0].textContent = days);
container.children[2].classList.contains('hours') &&
(container.children[2].textContent = hours);
container.children[4].classList.contains('minutes') &&
(container.children[4].textContent = minutes);
container.children[6].classList.contains('seconds') &&
(container.children[6].textContent = seconds);
});
};
const timerInterval = setInterval(updateCountdown, 1000);
updateCountdown();
If the user doesn't specify a future date on the backend, I'd like to use a fallback which automatically sets the countdown timer to the upcoming Sunday at 9am. To solve this I tried setting a standardized countDownDate variable but I'm having trouble coming up with a way to set the day and time to automatically be the upcoming Sunday at 9am.

It seems you just want a way to set a date to next Sunday at 09:00. In plain JS it might be:
/* Get a date for next occurence of Sunday at 09:00
*
* #param {Date} d - start date, default is current date and time
* #returns {Date} for next Sunday at 09:00 after d
*/
function getNextSunday(d = new Date()) {
// Create date for next Sun at 9
let sun = new Date(d.getFullYear(), d.getMonth(), d.getDate() + (7 - (d.getDay() || 7)), 9, 0, 0, 0);
// If date is same day but later, move to next Sun
sun <= d? sun.setDate(sun.getDate() + 7) : null;
return sun;
}
// Examples
// Next Sunday at 9:00
console.log(getNextSunday().toString());
// Next Sunday after Sun 7 Feb at 8:59:59
console.log(getNextSunday(new Date(2021, 1, 7, 8, 59, 59)).toString());
// Next Sunday after Sun 7 Feb at 9:00
console.log(getNextSunday(new Date(2021, 1, 7, 9)).toString());
Notes:
d.getDay() || 7 is used so that if getDay returns 0 (Sunday), it's replaced with the number 7. That means the expression sets sun to the current day if it's Sunday or the next Sunday if it isn't. Otherwise on Sundays it would create a Date for the previous Sunday.
sun <= d? sun.setDate(sun.getDate() + 7) : null is used so that if the current date is Sunday but the time is after the specified time (in this case 9:00) the date is moved to the following Sunday at 9:00. Using the compound ? : operator this way is just a another way of writing if (sun <= d) sun.setDate(sun.getDate() + 7) on one line. I prefer if statements to be followed by a block and would rather use ? : for simple expressions on single lines.

Moment.js might be what you are looking for.
moment().endOf('week').add(1, 'second').add(9, 'hours').toString()
End of week uses the locale aware week start day, so you might have to configure it if you have users globally.
Edit
Alternatively, if you do not want to use external libraries you can check the current weekday and hour with the JavaScript Date object.
const day = countDownDate.getDay() // weekday sun = 0, sat = 6
const hour = countDownDate.getHours() // 0 - 23
Within your logic, you would want to get the distance between day and 0 (also accounting for countDownDate starting on a Sunday) and hour and 9. However, another way to implement this is to check the current date upon each update which could reduce the error in case the interval gets interrupted.

Related

Take difference between two dates from Monday to Sunday

I have a simple Javascript function that calculates the weeks between two dates, every seven days equals one week:
function getWeeksDiff(startDate, endDate) {
const msInWeek = 1000 * 60 * 60 * 24 * 7;
return Math.round(Math.abs(endDate - startDate) / msInWeek);
}
It works correctly, but now I want to change the logic. I want to take a week from every Monday to Saturday regardless of the day it is on and that is not strictly related to 7 days
For example: Today is November 23, so 24,25,26 and 27 must count 1 week, and from Monday 28 to Sunday 4 it must count two weeks, so my logic is as follows:
const week = 1000 * 60 * 60 * 24 * 7;
const day = 24 * 60 * 60 * 1000;
function weeksBetween(startDate, endDate) {
return Math.ceil((weekStart(endDate) - weekStart(startDate)) / week) + 1;
}
function weekStart(dt) {
const weekday = dt.getDay();
return new Date(dt.getTime() - Math.abs(0 - weekday) * day);
}
But for some reason, every Sunday it calculates one more week, for example, Sunday the 27th already shows me two weeks instead of one:
const week = 7 * 24 * 60 * 60 * 1000;
const day = 24 * 60 * 60 * 1000;
function weekStart(dt) {
const weekday = dt.getDay();
return new Date(dt.getTime() - Math.abs(0 - weekday) * day);
}
function weeksBetween(d1, d2) {
return Math.ceil((weekStart(d2) - weekStart(d1)) / week)+1;
}
console.log(weeksBetween(new Date("11/23/2022"), new Date("11/27/2022")));
function countWeeks(startDate, endDate) {
// Calculate the next Sunday from the startDate
// Change the 0 if you need a different day to start the week
const firstWeek = startDate.getDate() + (7 + 0 - startDate.getDay()) % 7;
const newStartDate = new Date(startDate.setDate(firstWeek));
// Remove that partial week and calculate how many partial weeks remaining
return Math.ceil((endDate-newStartDate)/1000/60/60/24/7) + 1;
}
const date1 = new Date('11/20/2022');
const date2 = new Date('11/27/2022');
console.log(countWeeks(date1, date2)); // 2
const date3 = new Date('11/1/2022');
const date4 = new Date('11/29/2022');
console.log(countWeeks(date3, date4)); // 5
Credit where credit is due: https://codereview.stackexchange.com/questions/33527/find-next-occurring-friday-or-any-dayofweek
function weeksBetween(startDate, endDate) {
const dayTimestamp = 24 * 3600 * 1000
//count how many days between 2 dates
const days = 1 + Math.round((endDate - startDate) / dayTimestamp);
//get the day difference from the current date with Monday
const dayDiff = (startDate.getDay() + 6) % 7
//the default count is always 1, and calculate how many full weeks go from the start date to the end date
//a full week will be 7 days
return 1 + Math.floor((days + dayDiff) / 7);
}
console.log("11/22/2022 - 11/24/2022:",weeksBetween(new Date("11/22/2022"), new Date("11/24/2022")));
console.log("11/21/2022 - 11/28/2022:",weeksBetween(new Date("11/21/2022"), new Date("11/28/2022")));
console.log("11/21/2022 - 11/29/2022:",weeksBetween(new Date("11/21/2022"), new Date("11/29/2022")));
console.log("11/23/2022 - 12/04/2022:",weeksBetween(new Date("11/23/2022"), new Date("12/04/2022")));

How to get 24:XX time from datetime?

Is there a DateTime Format that allows representation of the 24-hour clock to roll over where 24:XX is valid time?
For example
const secondsToTimeOfDay = (totalSeconds: number): string => {
return new Date(totalSeconds * 1000).toISOString().substr(11, 8);
};
var x = secondsToTimeOfDay(86399)
console.log(x)
Returns
23:59:59
But when seconds are greater than 86400 (The number of seconds in one day) it starts on the next day?
Example
var x = secondsToTimeOfDay(87000)
console.log(x)
Returns
00:10:00
Is there a date format that will return in a 24:xx format?
Example (I know this works but I want to know if it can be done using some kind of built-in Date Object)
const SomeNewFunction = (totalSeconds: number): string => {
var duration = 1000*totalSeconds
var milliseconds = parseInt((duration % 1000) / 100),
seconds = Math.floor((duration / 1000) % 60),
minutes = Math.floor((duration / (1000 * 60)) % 60),
hours = Math.floor((duration / (1000 * 60 * 60)));
hours = (hours < 10) ? "0" + hours : hours;
minutes = (minutes < 10) ? "0" + minutes : minutes;
seconds = (seconds < 10) ? "0" + seconds : seconds;
return hours + ":" + minutes + ":" + seconds;
}
var x = SomeNewFunction(87000)
var y = SomeNewFunction(97000)
console.log(x)
console.log(y)
Returns
24:10:00
26:56:40
Where the SomeNewFuntion uses some kind of DateTimeObject rather than math?
The JavaScript Date object represents a single instant in the history of the world, both date and time. While you can ignore the date part in display, it is always there - new Date(300000) doesn't represent "00:05:00 on any day", it represents "00:05:00 on January 1st 1970, according to UTC".
Since January 1st 1970 didn't have a 25th and 26th hour, the format you're asking for wouldn't make sense. Put a different way, "Feb 2nd 02:00" and "Feb 1st 26:00" are the same instant in time, but (if I understand your problem correctly) you want to be able to represent them distinctly.
There are time-related objects where "the 26th hour" would make sense:
A "duration", representing an absolute amount of time, independent of when it happens.
An "interval", representing the span of time between two specific instants.
A "time of day", in certain specialised cases, where you want to consider the "day" to last more than 24 hours for planning purposes.
JavaScript doesn't currently have any of those built-in, although there are libraries that do, and a proposal for adding them natively.
It's likely that most "time of day" implementations would not allow for more than 24 hours in the day, but you could represent it using a "duration" or "interval". The end result might look something like this:
var timetableEntry = {
"date": Temporal.PlainDate.from({year: 2006, month: 8, day: 24}),
"startOffsetFromMidnight": Temporal.Duration.from({ hours: 23, minutes: 30 }),
"endOffsetFromMidnight": Temporal.Duration.from({ hours: 26, minutes: 30 })
}
var journeyDuration = timetableEntry.endOffsetFromMidnight.subtract( timetableEntry.startOffsetFromMidnight );
var startDateTime = timetableEntry.date.add( timetableEntry.startOffsetFromMidnight );
var endDateTime = timetableEntry.date.add( timetableEntry.endOffsetFromMidnight);

Not counting down time correctly

I am creating a countdown website. I want it to count down from the current time the my celebration date 2019-11-29 00:00:00. I want the timezone to be in Australia Brisbane time zone. However, it seems to be that it keeps calculating the amount of time left till my celebration date wrong. Could someone tell me what I did wrong? And once the time hits 0, how can I get rid of the countdown and replace it with Its celebration time
function dateDiff(a, b) {
// Some utility functions:
const getSecs = dt => (dt.getHours() * 24 + dt.getMinutes()) * 60 + dt.getSeconds();
const getMonths = dt => dt.getFullYear() * 12 + dt.getMonth();
// 0. Convert to new date objects to avoid side effects
a = new Date(a);
b = new Date(b);
if (a > b) [a, b] = [b, a]; // Swap into order
// 1. Get difference in number of seconds during the day:
let diff = getSecs(b) - getSecs(a);
if (diff < 0) {
b.setDate(b.getDate()-1); // go back one day
diff += 24*60*60; // compensate with the equivalent of one day
}
// 2. Get difference in number of days of the month
let days = b.getDate() - a.getDate();
if (days < 0) {
b.setDate(0); // go back to (last day of) previous month
days += b.getDate(); // compensate with the equivalent of one month
}
// 3. Get difference in number of months
const months = getMonths(b) - getMonths(a);
return {
years: Math.floor(months/12),
months: months % 12,
days,
hours: Math.floor(diff/3600),
minutes: Math.floor(diff/60) % 24,
seconds: diff % 60
};
}
// Date to start on
var celebrationDate = new Date("2019-11-29 00:00:00").toLocaleString("en-US", {timeZone: "Australia/Brisbane"});
// Update the count every 1 second
!function refresh () {
const diff = dateDiff(new Date().toLocaleString("en-US", {timeZone: "Australia/Brisbane"}), celebrationDate);
document.getElementById("day-val").innerHTML = diff[Object.keys(diff)[2]];
document.getElementById("hour-val").innerHTML = diff[Object.keys(diff)[3]];
document.getElementById("min-val").innerHTML = diff[Object.keys(diff)[4]];
document.getElementById("sec-val").innerHTML = diff[Object.keys(diff)[5]];
setTimeout(refresh, 1000)
}()
<div id="day-val"></div><div>Days</div><br>
<div id="hour-val"></div><div>Hours</div><br>
<div id="min-val"></div><div>Minutes</div><br>
<div id="sec-val"></div><div>Seconds</div>
IF you are using moment.js
What you can do is use the diff and duration functions like so:
const now = moment();
const celebrationDate= moment("2019-11-30 00:00:00");
// this will get the difference between now and celebrationDate
const celebrationDateDiff = expiration.diff(now);
// convert it into duration
const duration = moment.duration(celebrationDateDiff);
You can then access the countdown like:
duration.days() // will return the number of days
duration.hours() // will return the number of remaining hours
duration.minutes() // will return the number of remaining minutes
Solution with plain javascript:
Get the clients time zone offset
Calculate the diff between client offset and "Australian/Brisbane" offset
Calculate time left by targetDate - currentDate - offsetDiff
Format the result to display it
Example:
const minToMS = min => min * 60 * 1000
const getCountDownFromMS = diff => {
const milliseconds = diff % 1000
diff = (diff - milliseconds) / 1000
const seconds = diff % 60
diff = (diff - seconds) / 60
const minutes = diff % 60
diff = (diff - minutes) / 60
const hours = diff % 24
diff = (diff - hours) / 24
const days = diff
return {
days,
hours,
minutes,
seconds,
milliseconds
}
}
const targetDate = new Date('2019-11-29 00:00:00')
// Getting offset with getTimezoneOffset is not reliable, using a database to get time zone offset is needed, like IANA Time Zone Database.
// moment.js internally uses IANA db
const tzOffset = targetDate.getTimezoneOffset() // Client browsers timezone offset in minutes
const target_tzOffset = -600 // Australia Brisbane timezone offset in minutes
const offset = minToMS(tzOffset - target_tzOffset)
const msLeft = targetDate - new Date() - offset
const result = getCountDownFromMS(msLeft)
You should also check moment.js as it has many useful features.
Example with moment.js:
const now = moment()
const target = moment.tz('2019-11-29 00:00:00', 'Australia/Brisbane')
const result = moment.duration(target.diff(now))

How to countdown and divide the time into segments?

I wish to do a countdown to a specific date and hour (January 10, 2018, 19:30). Which in large part I am able to do. Below code shows the remaining days, hours, minutes and seconds.
The tricky bit is to get certain periods of time. The countdown should respond to the following:
1. on the deadline day and hour show the message 'Now going live'. Which is 10 January 2018 19:30.
2. That same day but BEFORE 19:30 it should say 'Going live tonight'
3. The complete day before the deadline day (from 00:00 to 23:59) it should say 'last day'
4. The complete days before that it should say 'many days to go'
Step 1 and 2 I managed, but I'm having trouble getting the complete day before the deadline day and the complete days before that. That's because I'm not able to define the complete day before the deadline day (and the days before that). Because it counts '1 day' as 1 day before 10 January 19:30 (so it also takes those hours/minutes of 19:30 into account).
Step 1 and 2 I managed in the if-loop, but I can't figure out how to do step 3 and 4. Step 3 should say something like 'count one day, but before 10 January 2018 00:00. So it should subtract that 19:30 to get to 9 januari 2018 00:00-23:59. And the same for step 4. Can someone fix my code?
// Get todays date and time
var now = new Date().getTime();
// Set the date we're counting down to
var countDownDate = new Date("Januari 10, 2018 19:30").getTime();
// Find the distance between now an the count down date
var distance = countDownDate - now;
// Time calculations for days, hours, minutes and seconds
var days = Math.floor(distance / (1000 * 60 * 60 * 24));
var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
var seconds = Math.floor((distance % (1000 * 60)) / 1000);
// Display the result
this.timeleft.text = days + "d " + hours + "h " + minutes + "m " + seconds + "s ";
// countdown day 19:30
if ((days == 0) && (hours == 0) && (minutes == 0)) {
this.countdown.text = "NOW GOING LIVE!";
// countday day 00:00 - 19.30
} else if ((days == 0) && (hours <= 19) && (minutes <= 30)) {
this.countdown.text = "GOING LIVE TONIGHT!";
// 9 January 00:00 - 23:59
} else if ((days <= 1) && (hours >= 19) && (minutes >= 30)) {
this.countdown.text = "LAST DAY";
// days before 10 January
} else if (days >= 1) {
this.countdown.text = "MANY DAYS TO GO";
}
Since the "deadline" is hard-coded, you can hard-code everything and end up with something very simple:
var now = new Date().getTime();
var lastDayThreshold = new Date("January 9, 2018 00:00").getTime();
var liveTonightThreshold = new Date("January 10, 2018 00:00").getTime();
var countDownDate = new Date("January 10, 2018 19:30").getTime();
if (now < lastDayThreshold) this.countdown.text = "MANY DAYS TO GO";
else if(now < liveTonightThreshold) this.countdown.text = "LAST DAY";
else if(now < countDownDate) this.countdown.text = "LIVE TONIGHT";
else this.countdown.text = "NOW GOING LIVE";
Alex's answer was indeed what I was after. Those 'treshhold times' did the trick. Was thinking about improving it though as now I have to hard-code three dates/times. Preferably I would like to only specify the countDownDate date/time. And then let both Threshold dates calculate themselves. I tried to do that in a way, but ran into a problem. I know how to specify one day (1000 * 60 * 60 * 24), so I could subtract this 'oneday' value to get to the day before. But I wasn't able to calculate the milliseconds for the specified time 19:30. In order to read the miilliseconds since the beginning of January 10 until January 10 19:30. If I were able to do that it would look something like this (though I know this is incorrect, but you'll get the idea):
var oneday = 1000 * 60 * 60 * 24;
var countDownDate = new Date("January 10, 2018 19:30").getTime();
var lastDayThreshold = new Date(countDownDate - oneday "00:00").getTime();
var liveTonightThreshold = new Date(countDownDate "00:00").getTime();
You'll see my problem: for lastDayTreshold I could subtract one day of the countdowndate but then it would consider that 19:30 the previous day, not 00:00. And for liveTonightThreshold I also couldn't specify that I mean 00:00 of the countdowndate.
Would there be a way of doing that? Then I would just have to specify the countdown day and time and the rest would figure them out themselves.

Get next occurrence of day and hour in javascript

I am attempting to implement a countdown timer to a specific point in time in the future. This point in time is always the same day of the week and hour, and is based on UTC time.
I am attempting to write a general function that given a day of the week and an hour, it will return a date object that represents that criteria in the future.
Examples:
getNextOccuranceOfUTCDayAndHour(1, 7);
Get the next occurrence of 7 am on Monday. If today is Monday, 5/25/2015 # midnight UTC, then this function should return a Date object representing Monday 6/1/2015 7 am UTC.
getNextOccuranceOfUTCDayAndHour(3, 13);
Get the next occurrence of 1 pm on Wednesday. If today is Tuesday, 5/26/2015 # midnight UTC, then this function should return a Date object representing Wednesday 5/27/2015 1 pm UTC.
I have attempted to write a function to do this and I have included the snippet below, but it only seems to work for some dates and not others. It's incredibly unreliable. I would prefer not to use Moment.js.
function getNextOccuranceOfUTCDayAndHour(day, hour) {
d = new Date();
d.setDate(d.getUTCDate() + (7 + day - d.getUTCDay()) % 7)
d.setUTCHours(hour, 0, 0, 0);
return d;
}
function format_seconds(t) {
var d = Math.floor(t / 86400);
var h = Math.floor(t % 86400 / 3600);
var m = Math.floor(t % 3600 / 60);
var s = Math.floor(t % 3600 % 60);
return ((d > 0 ? d + " d. " : "") +
(h > 0 ? h + " h. " : "") +
(m > 0 ? m + " m. " : "") +
s + " s.");
}
function update() {
var next_occurance = getNextOccuranceOfUTCDayAndHour(1, 7);
$('#next_occurance').text(next_occurance);
var ms = next_occurance - new Date();
$('#countdown').text(format_seconds(Math.floor(ms / 1000)));
}
$(function() {
update();
setInterval(update, 1000);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p id="next_occurance">Next Occurance</p>
<p id="countdown">Countdown</p>
Edit: Some examples of expected vs. returned values. JSFiddle
The problem in the original code was the use of d.setDate(); instead of d.setUTCDate();. Additionally, if the current day was the same day of week as the target day, then the result was incorrect. Simply adding an if statement to check for this case fixes that problem.
Updated JSFiddle: https://jsfiddle.net/2j49q0ak/1

Categories

Resources