Changing Dates so They Are Within Business Hours - javascript

In a JavaScript step in Pentaho Data Integration, I want calculate the time in hours which passes between one date and another.
After following along with this this blog post, I realize that I need to adjust the startDate and endDate values in the function below which fall outside business hours so that they're within business hours so the function doesn't return zero. The dates are in the format 09/27/2018 18:54:55.
Here's my attempt so far:
var Approve_Gap;
var created_at_copy;
var approved_at_copy1;
// Function that accepts two parameters and calculates
// the number of hours worked within that range
function workingHoursBetweenDates(startDate, endDate) {
// Store minutes worked
var minutesWorked = 0;
// Validate input
if (endDate < startDate) { return 0; }
// Loop from your Start to End dates (by hour)
var current = startDate;
// Define work range
var workHoursStart = 8;
var workHoursEnd = 17;
var includeWeekends = true;
// bring dates into business hours
if(current.getHours() > workHoursEnd) {
current = current - (current.getHours() - workHoursEnd);
}
else if(current.getHours() < workHoursStart) {
current = current + (workHoursStart - current.getHours())
}
if(endDate.getHours() > workHoursEnd) {
endDate = endDate - (endDate.getHours() - workHoursEnd);
}
else if(endDate.getHours() < workHoursStart) {
endDate = endDate + (workHoursStart - endDate.getHours())
}
// Loop while currentDate is less than end Date (by minutes)
while(current <= endDate){
// Is the current time within a work day (and if it
// occurs on a weekend or not)
if(current.getHours() >= workHoursStart && current.getHours() < workHoursEnd && (includeWeekends ? current.getDay() !== 0 && current.getDay() !== 6 : true)){
minutesWorked++;
}
// Increment current time
current.setTime(current.getTime() + 1000 * 60);
}
// Return the number of hours
return minutesWorked / 60;
}
Approve_Gap = workingHoursBetweenDates(created_at_copy, approved_at_copy1);

I got the dates into business hours by adjusting copies of the dates as shown below:
if(created_at_copy.getHours() >= workHoursEnd) {
created_at_copy.setDate(created_at_copy.getDate() + 1);
created_at_copy.setHours(8);
created_at_copy.setMinutes(0);
created_at_copy.setSeconds(0);
} else if(created_at_copy.getHours() < workHoursStart) {
created_at_copy.setHours(8);
created_at_copy.setMinutes(0);
created_at_copy.setSeconds(0);
}
if(approved_at_copy1.getHours() >= (workHoursEnd)) {
approved_at_copy1.setDate(approved_at_copy1.getDate() + 1);
approved_at_copy1.setHours(8);
approved_at_copy1.setMinutes(0);
created_at_copy.setSeconds(0);
} else if(approved_at_copy1.getHours() < workHoursStart) {
approved_at_copy1.setHours(8);
approved_at_copy1.setMinutes(0);
created_at_copy.setSeconds(0);
}

Related

Display div based on remote timezone and dates

I'm trying to display a chat div that displays between the hours of 8am-6pm Monday to Friday "Online" or show nothing if offline, based on the Eastern Time Zone (NYC), so that customers from Beijing will see Online or Offline based on these hours.
Simply need to show() or hide() the div. So far I have the hours, but I'm not sure how to get them to be in relation to the user time-zone.
$(document).ready(function () {
var start = new Date();
var end = new Date();
var time = new Date().getTime();
if (time > start.setHours(8,00) && time < end.setHours(18,00)) {
$('.online').show();
}
else {
$('.offline').hide();
}
});
The previous answer (seen in edit history) was to use the offset from UTC, however that isn't going to be an option if you want to support Daylight Savings; which is an important thing to do.
As such, the modification to the previous suggestion completely removes the use of UTC. To support daylight savings, the only proper way to get the time from EST is going to be to set the locale to that location, read the time, set up a new date object (which will technically be set up in the client local, but all we really want from it are the day and hour response from the Date object so we will ignore that technicality).
This is done by passing an object with the toLocaleString call which specifies the timezone, and then constructing a new date with the result of that.
var NYDate = new Date(new Date().toLocaleString("en-US", {timeZone: "America/New_York"}));
var NYHour = NYDate.getHours();
var NYDay = NYDate.getDay()
if (NYHour >= 8 && NYHour <= 18 &&
NYDay > 0 && NYDay < 6) {
$('.online').show();
}else {
$('.online').hide();
}
.online {
display: none;
color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="online">Online</div>
No JavaScript needed. You should do this from the server side. (The customer doesn’t tell the store when it’s open, the store tells the customer!)
Assuming your HTML is being generated by some server-side language (PHP, Ruby, etc), set the program to use New York time, and simply calculate if you’re within the “open” times. If you’re open, generate the Chat div. if you’re closed... don’t.
(Or alternately, show/hide it via CSS and classes)
Turns out that this is not a completely trivial task using JavaScript (as noted in the answer from #StephenR, this may be easier to deal with server side). And as noted in some of the comments, using a library may be the better js approach.
That said, after thinking a bit about the comments from #RobG regarding varying browser support for options like timeZone in toLocaleString, I was curious what it would take to solve this another way (makes me grateful for the various js date libraries). Snippet below...
const getOffset = (month, date, day, hour) => {
// assume EST offset
let offset = 5;
// adjust to EST offset as needed
if ((month > 2 && month < 10) || (month === 2 && date > 14)) {
offset = 4;
} else if (month === 2 && date > 7 && date < 15) {
if ((day && date - day > 7) || (day === 0 && hour - offset >= 2)) {
offset = 4;
}
} else if (month === 10 && date < 8) {
if ((day && date - day < 0) || (day === 0 && hour - offset < 1)) {
offset = 4;
}
}
return offset;
};
const isOnline = () => {
const dt = new Date(); // current datetime
let year = dt.getUTCFullYear(); // utc year
let month = dt.getUTCMonth(); // utc month (jan is 0)
let date = dt.getUTCDate(); // utc date
let hour = dt.getUTCHours(); // utc hours (midnight is 0)
let day = dt.getUTCDay(); // utc weekday (sunday is 0)
let offset = getOffset(month, date, day, hour);
if (hour - offset < 0) {
hour = 24 + hour - offset;
day = day ? day - 1 : 6;
if (date === 1) {
if (!month) {
year -= 1;
month = 11;
} else {
month -= 1;
}
date = new Date(year, month + 1, 0).getDate();
} else {
date -= 1;
}
} else {
hour -= offset;
}
if (day > 0 && day < 6 && hour > 7 && hour < 19) {
return true;
}
return false;
};
if (isOnline()) {
console.log('online'); // handle online
} else {
console.log('offline'); // handle offline
}

Calculate days excluding weekend and holiday in JavaScript

I am trying to write a code where total days will be counted excluding weekends and custom defined holiday. I searched through stackoverflow and adobe forum to find a solution and came with below code.
If public holiday falls in a working day (Saturday-Wednesday) it is excluding from calculation.
My problem is that if public holiday falls in weekend (Thursday-Friday), it is deducting for both (holiday & weekend). Suppose leave duration is 18/09/2018-22/09/2018, total count 2 days is showing in place of 3. Again for 17/10/2018-21/10/2018, total count 1 day is showing in place of 3 days.
Any help or any idea to solve the problem would be great!
Regards
//Thursday and Friday will be excluded as weekend.
var start = this.getField("From").value;
// get the start date value
var end = this.getField("To").value;
var end = util.scand("dd/mm/yyyy H:MM:SS", end + " 0:00:00");
var start =util.scand("dd/mm/yyyy H:MM:SS", start + " 0:00:00");
event.value = dateDifference(start, end);
function dateDifference(start, end) {
// Copy date objects so don't modify originals
var s = new Date(+start);
var e = new Date(+end);
// Set time to midday to avoid daylight saving and browser quirks
s.setHours(12,0,0,0);
e.setHours(12,0,0,0);
// Get the difference in whole days
var totalDays = Math.round((e - s) / 8.64e7);
// Get the difference in whole weeks
var wholeWeeks = totalDays / 7 | 0;
// Estimate business days as number of whole weeks * 5
var days = wholeWeeks * 5;
// If not even number of weeks, calc remaining weekend days
if (totalDays % 7) {
s.setDate(s.getDate() + wholeWeeks * 7);
while (s < e) {
s.setDate(s.getDate() + 1);
// If day isn't a Thursday or Friday, add to business days
if (s.getDay() != 4 && s.getDay() != 5) {
++days;
}
}
}
var hdayar = ["2018/02/21","2018/03/17","2018/03/26","2018/04/14","2018/05/01","2018/08/15","2018/09/2 1","2018/10/18","2018/10/19","2018/12/16","2018/12/25"];
//test for public holidays
var phdays = 0;
for (var i = 0; i <hdayar.length; i++){
if ((Date.parse(hdayar[i]) >= Date.parse(start)) && (Date.parse(hdayar[i]) <= Date.parse(end))) {phdays ++;}}
return days-phdays + 1;
}
You should use a library for this rather than reinventing the wheel.
But if you want to do it yourself you could use .getDay to check if the public holidays are on a weekend.
var weekend = [4, 5], // for Thursday, Friday
holDate, holDay;
for (var i = 0; i < hdayar.length; i++){
holDate = Date.parse(hdayar[i]);
holDay = new Date(holDate).getDay()
if (weekend.indexOf(holDay) == -1 && holDate >= Date.parse(start) && holDate <= Date.parse(end)) {
phdays ++;
}
}
phdays will now contain the number of non-weekend public holidays within the range.
Just have the same requirement and this is the my work around.Hope it helps other
var holiday = ["4/18/2019", "4/19/2019", "4/20/2019", "4/25/2019", "4/26/2019"];
var startDate = new Date();
var endDate = new Date(startDate.setDate(startDate.getDate() + 1));
for (i = 0; i < holiday.length; i++) {
var date = endDate.getDate();
var month = endDate.getMonth() + 1; //Months are zero based
var year = endDate.getFullYear();
if ((month + '/' + date + '/' + year) === (holiday[i])) {
endDate = new Date(endDate.setDate(endDate.getDate() + 1));
if (endDate.getDay() == 6) {
endDate = new Date(endDate.setDate(endDate.getDate() + 2));
} else if (endDate.getDay() == 0) {
endDate = new Date(endDate.setDate(endDate.getDate() + 1));
}
}
}
Here, end date gives you next working day.Here,I'm ignoring current day and start comparing from Next day whether it's holiday or weekend.You can customize dateTime as per your requirement (month + '/' + date + '/' + year).Careful whenever you compares two dates with each other. Because it looks same but actually it's not.So customize accordingly.

Dynamically create/limit range for random time and convert to timestamp

I have been working on a script that dynamically creates a date/time value between two days. However I want to limit it as follows:
if appointment.status === "today": Set the range between now (start) and the end of the working day (end) i.e. today between time right now to 18:00
if appointment.status === "pending": Set the range from tomorrow (start) + 1 week (end) but keeping in mind the working day i.e. 08:00-17:00... so it can be next week Tuesday 13:00
Once done, I would like to convert var date to a timestamp.
This is my code so far:
if (appointment.status === "today") {
appointment.timestamp = (function() {
var a = randomTime(new Date("10-10-2015 10:30"), new Date("12-10-2015 02:10"));
return a
})();
} else if (appointment.status === "pending") {
appointment.timestamp = (function() {
var a = randomTime(new Date("10-10-2015 10:30"), new Date("12-10-2015 02:10"));
return a
})();
}
function randomTime(start, end) {
var diff = end.getTime() - start.getTime();
var new_diff = diff * Math.random();
var date = new Date(start.getTime() + new_diff);
return date;
}
var start = new Date();
var end = new Date();
if (appointment.status === "today") {
end.setHours(18);
end.setMinutes(0);
} else if (appointment.status === "pending") {
start.setDate(start.getDate() + 1);
end.setDate(end.getDate() + 7);
}
start = restrictTimeToWorkHours(start);
end = restrictTimeToWorkHours(end);
appointment.timestamp = Math.floor(randomTime(start, end) / 1000);
function randomTime(start, end) {
var diff = end.getTime() - start.getTime();
var new_diff = diff * Math.random();
return new Date(start.getTime() + new_diff);
}
function restrictTimeToWorkHours(date) {
if (date.getHours() < 8) {
date.setHours(8);
date.setMinutes(0);
}
if (date.getHours() > 16) {
date.setHours(16);
date.setMinutes(0);
}
return date;
}
The key thing to remember here is the Math.floor(randomTime(start, end) / 1000);. You said you wanted it in timestamp, so I take it to mean you want a Unix Timestamp. A Unix Timestasmp is in seconds, while Date.getTime() is in milliseconds, so we need to divide by 1000 to get seconds
Either look for a date library or create your own date transformation function for each operation and combine those.
endOfDay(time)
getRandomTimeBetween(startTime, endOfDay(startTime))
isWorkingDay(time)
// etc.
For the second case, you can do it in two steps: select a random day in the range, then select a random time within the working hours of that day.
Break it down to simple, logical operations, and you can test each function separately, and your code will look nice and readable.

comparing two time values in an if statement

i need to compare two values and do stuff with the result of the comaprison depending on if the times are more than 1 hour apart.
So for example
Time1 = 13:07:01
Now = 13:26:47
what i need to do is in an if statement compare to see if Now is an hour ahead of Time one for example
If( Comparison){
1 hour has passed
}else{
1 hour has not passes
}
What would be the best way to do this?
Use Date() - MDN
if (Date.now() > new Date(oldDate) + 1000 * 60 * 60) {
console.log('1 hour has passed');
} else {
console.log('1 hour has not passed');
}
<script>
function checkForOneHr(){
var start = '5:30';
var end = '6:50';
s = start.split(':');
e = end.split(':');
min = e[1]-s[1];
hour_carry = 0;
if(min < 0){
min += 60;
hour_carry += 1;
}
hour = e[0]-s[0]-hour_carry;
if(hour >= 1)
return true;
else
return false;
}
if( checkForOneHr()){
alert('1 hour has passed');
}else{
alert(' hour has not passes ');
}
</script>
If they are Date objects
var now = new Date();
var diff = now.getTime() - time1.getTime();
if(diff >= 1000*60*60) {
console.log('an hour has passed');
} else {
console.log('an hour has not passed');
}
First of All , Convert Both the times to string format using,
strtotime().
See any PHP manual.
Take out the difference of them.
The result will be difference in seconds.
Divide the difference by 3600.
If it is greater then 1, do your 'Code to do".
eg.
$current_time = date('T-m-d H:I:S');
$time_now = strtotime($current_time);

How to exclude weekends between two dates using Moment.js

I am trying to exclude weekends in my JavaScript code. I use moment.js and having difficulty choosing the right variable for 'days'.
So far I have thought that I need to exclude day 6 (saturday) and day 0 (sunday) by changing the weekday variable to count from day 1 to day 5 only. But not sure how it changes.
My jsfiddle is shown here: FIDDLE
HTML:
<div id="myContent">
<input type="radio" value="types" class="syncTypes" name="syncTypes"> <td><label for="xshipping.xshipping1">Free Shipping: (<span id="fsv1" value="5">5</span> to <span id="fsv2" value="10">10</span> working days)</label> </td><br>
<div id="contacts" style="display:none;border:1px #666 solid;padding:3px;top:15px;position:relative;margin-bottom:25px;">
Contacts
</div>
<input type="radio" value="groups" class="syncTypes" name="syncTypes"> <td><label for="xshipping.xshipping2">Express Shipping: (<span id="esv1" value="3">3</span> to <span id="esv2" value="4">4</span> working days)</label> </td>
<div id="groups" style="display:none;border:1px #666 solid;padding:3px;top:15px;position:relative">
Groups
</div>
</div>
JavaScript:
var a = 5; //Free shipping between a
var b = 10;//and b
var c = 3;//Express shipping between c
var d = 4;//and d
var now = moment();
var f = "Your item will be delivered between " + now.add("days",a).format("Do MMMM") + " and " + now.add("days",b).format("Do MMMM");
var g = "Your item will be delivered between " + now.add("days".c).format("Do MMMM") + " and " + now.add("days",d).format("Do MMMM");
var h = document.getElementById('contacts');
h.innerHTML = g
var i = document.getElementById('groups');
i.innerHTML = f
$(function() {
$types = $('.syncTypes');
$contacts = $('#contacts');
$groups = $('#groups');
$types.change(function() {
$this = $(this).val();
if ($this == "types") {
$groups.slideUp(300);
$contacts.delay(200).slideDown(300);
}
else if ($this == "groups") {
$contacts.slideUp(300);
$groups.delay(200).slideDown(300);
}
});
});
Here you go!
function addWeekdays(date, days) {
date = moment(date); // use a clone
while (days > 0) {
date = date.add(1, 'days');
// decrease "days" only if it's a weekday.
if (date.isoWeekday() !== 6 && date.isoWeekday() !== 7) {
days -= 1;
}
}
return date;
}
You call it like this
var date = addWeekdays(moment(), 5);
I used .isoWeekday instead of .weekday because it doesn't depend on the locale (.weekday(0) can be either Monday or Sunday).
Don't subtract weekdays, i.e addWeekdays(moment(), -3) otherwise this simple function will loop forever!
Updated JSFiddle http://jsfiddle.net/Xt2e6/39/ (using different momentjs cdn)
Those iteration looped solutions would not fit my needs.
They were too slow for large numbers.
So I made my own version:
https://github.com/leonardosantos/momentjs-business
Hope you find it useful.
https://github.com/andruhon/moment-weekday-calc plugin for momentJS might be helpful for similar tasks
It does not solves the exact problem, but it is able to calculate specific weekdays in the range.
Usage:
moment().isoWeekdayCalc({
rangeStart: '1 Apr 2015',
rangeEnd: '31 Mar 2016',
weekdays: [1,2,3,4,5], //weekdays Mon to Fri
exclusions: ['6 Apr 2015','7 Apr 2015'] //public holidays
}) //returns 260 (260 workdays excluding two public holidays)
If you want a pure JavaScript version (not relying on Moment.js) try this...
function addWeekdays(date, days) {
date.setDate(date.getDate());
var counter = 0;
if(days > 0 ){
while (counter < days) {
date.setDate(date.getDate() + 1 ); // Add a day to get the date tomorrow
var check = date.getDay(); // turns the date into a number (0 to 6)
if (check == 0 || check == 6) {
// Do nothing it's the weekend (0=Sun & 6=Sat)
}
else{
counter++; // It's a weekday so increase the counter
}
}
}
return date;
}
You call it like this...
var date = addWeekdays(new Date(), 3);
This function checks each next day to see if it falls on a Saturday (day 6) or Sunday (day 0). If true, the counter is not increased yet the date is increased.
This script is fine for small date increments like a month or less.
I would suggest adding a function to the moment prototype.
Something like this maybe? (untested)
nextWeekday : function () {
var day = this.clone(this);
day = day.add('days', 1);
while(day.weekday() == 0 || day.weekday() == 6){
day = day.add("days", 1);
}
return day;
},
nthWeekday : function (n) {
var day = this.clone(this);
for (var i=0;i<n;i++) {
day = day.nextWeekday();
}
return day;
},
And when you're done and written some tests, send in a pull request for bonus points.
d1 and d2 are moment dates passed as an argument to calculateBusinessDays
calculateBusinessDays(d1, d2) {
const days = d2.diff(d1, "days") + 1;
let newDay: any = d1.toDate(),
workingDays: number = 0,
sundays: number = 0,
saturdays: number = 0;
for (let i = 0; i < days; i++) {
const day = newDay.getDay();
newDay = d1.add(1, "days").toDate();
const isWeekend = ((day % 6) === 0);
if (!isWeekend) {
workingDays++;
}
else {
if (day === 6) saturdays++;
if (day === 0) sundays++;
}
}
console.log("Total Days:", days, "workingDays", workingDays, "saturdays", saturdays, "sundays", sundays);
return workingDays;
}
If you want a version of #acorio's code sample which is performant (using #Isantos's optimisation) and can deal with negative numbers use this:
moment.fn.addWorkdays = function (days) {
// Getting negative / positive increment
var increment = days / Math.abs(days);
// Looping weeks for each full 5 workdays
var date = this.clone().add(Math.floor(Math.abs(days) / 5) * 7 * increment, 'days');
// Account for starting on Saturdays and Sundays
if(date.isoWeekday() === 6) { date.add(-increment, 'days'); }
else if(date.isoWeekday() === 7) { date.add(-2 * increment, 'days'); }
// Adding / removing remaining days in a short loop, jumping over weekends
var remaining = days % 5;
while(remaining != 0) {
date.add(increment, 'days');
if(date.isoWeekday() !== 6 && date.isoWeekday() !== 7)
remaining -= increment;
}
return date;
};
See Fiddle here: http://jsfiddle.net/dain/5xrr79h0/
Edit: now fixed issue adding 5 days to a day initially on a weekend
I know this question was posted long ago, but in case somebody bump on this, here is optimized solution using moment.js:
function getBusinessDays(startDate, endDate){
var startDateMoment = moment(startDate);
var endDateMoment = moment(endDate)
var days = Math.round(startDateMoment.diff(endDateMoment, 'days') - startDateMoment .diff(endDateMoment, 'days') / 7 * 2);
if (endDateMoment.day() === 6) {
days--;
}
if (startDateMoment.day() === 7) {
days--;
}
return days;
}
const calcBusinessDays = (d1, d2) => {
// Calc all days used including last day ( the +1 )
const days = d2.diff(d1, 'days') + 1;
console.log('Days:', days);
// how many full weekends occured in this time span
const weekends = Math.floor( days / 7 );
console.log('Full Weekends:', weekends);
// Subtract all the weekend days
let businessDays = days - ( weekends * 2);
// Special case for weeks less than 7
if( weekends === 0 ){
const cur = d1.clone();
for( let i =0; i < days; i++ ){
if( cur.day() === 0 || cur.day() === 6 ){
businessDays--;
}
cur.add(1, 'days')
}
} else {
// If the last day is a saturday we need to account for it
if (d2.day() === 6 ) {
console.log('Extra weekend day (Saturday)');
businessDays--;
}
// If the first day is a sunday we need to account for it
if (d1.day() === 0) {
console.log('Extra weekend day (Sunday)');
businessDays--;
}
}
console.log('Business days:', businessDays);
return businessDays;
}
This can be done without looping between all dates in between.
// get nb of weekend days
var startDateMonday = startDate.clone().startOf('isoWeek');
var endDateMonday = endDate.clone().startOf('isoWeek');
var nbWeekEndDays = 2 * endDateMonday.diff(startDateMonday, 'days') / 7;
var isoDayStart = startDate.isoWeekday();
if (isoDayStart > 5) // starts during the weekend
{
nbWeekEndDays -= (8 - isoDayStart);
}
var isoDayEnd = endDate.isoWeekday();
if (isoDayEnd > 5) // ends during the weekend
{
nbWeekEndDays += (8 - isoDayEnd);
}
// if we want to also exlcude holidays
var startOfStartDate = startDate.clone().startOf('day');
var nbHolidays = holidays.filter(h => {
return h.isSameOrAfter(startOfStartDate) && h.isSameOrBefore(endDate);
}).length;
var duration = moment.duration(endDate.diff(startDate));
duration = duration.subtract({ days: nbWeekEndDays + nbHolidays });
var nbWorkingDays = Math.floor(duration.asDays()); // get only nb of complete days
I am iterating from start date to end date and only counting days which are weekdays.
const calculateBusinessDays = (start_date, end_date) => {
const d1 = start_date.clone();
let num_days = 0;
while(end_date.diff(d1.add(1, 'days')) > 0) {
if ([0, 6].includes(d1.day())) {
// Don't count the days
} else {
num_days++;
}
}
return num_days;
}

Categories