Angular Date array creation with date range and time intervals - javascript

I am trying to create an option for user to get possible visit times between two user-chosen dates, 8:00-17:00 every day.
For example: Between 1 Sep to 4 Sep, return [1.09.20 8:00, 1.09.20 8:15 ..., 4.09.20 16:45].
My initial thought was to create two loops, with one iterating through days, second times, but soon I realized Date is not really cooperating with me. Later on I stumbled upon this piece of code to generate just days, without iterating through times, but it was not working for me:
let dates: Date[];
const theDate = new Date(from);
while (theDate < to) {
dates = [...dates, new Date(theDate)];
theDate.setDate(theDate.getDate() + 1);
}
return dates;
Console: (both dates from and to are working, in the log they are shown to be defined)
Sun Aug 23 2020 00:00:00 GMT+0200 (Central European Summer Time)
Sat Aug 29 2020 00:00:00 GMT+0200 (Central European Summer Time)
core.js:4197 ERROR TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
at VisitService.getDatesBetween (visit.service.ts:31)
at PatientVisitComponent.findVisits (patient-visit.component.ts:88)
at PatientVisitComponent_Template_input_click_28_listener (patient-visit.component.html:49)
at executeListenerWithErrorHandling (core.js:14315)
at wrapListenerIn_markDirtyAndPreventDefault (core.js:14350)
at HTMLInputElement.<anonymous> (platform-browser.js:582)
at ZoneDelegate.invokeTask (zone-evergreen.js:399)
I am open to any pre-build tools, I just need to later on filter this array, to exclude times (saved as Date, but I could change that) that are already taken, and let user to select one to book a visit.
EDIT: Whoops, forgot to add =[]; to the date, so it works, but only for the days. Is there any more elegant solution to do with times?

I would prefer moment instead of Date object but just so we are here, here is how I would do it with Date Object. So first this, the Date object accepts input in the format( Year, Month, Days). You have to follow this in order for you date to output correctly.
let fromMonth = "12";
let fromDay = "02";
let fromYear = "2020";
let toMonth = "12";
let toDay = "05";
let toYear = "2020";
let from = new Date(fromYear+"-"+fromMonth+"-"+fromDay);
let to = new Date(toYear+"-"+toMonth+"-"+toDay);
let dates = [];
while (from < to) {
dates = [...dates, new Date(from)];
from.setDate(from.getDate() + 1);
}
console.log(dates);
In the example above, I have separated days, months, and years for ease of input. As you can as you gradually change the day of either one of them the output will change at per your requirement.
Moment Version:
let fromDate = "05-12-2020";
let toDate = "11-12-2020";
//No need to input date in specific format with moment
let from = moment(fromDate).format('DD-MM-YYYY');
let to = moment(toDate).format('DD-MM-YYYY');
let dates = [];
while (moment(from).isBefore(to)) {
dates = [...dates, new Date(from)];
from = moment(from).add(1,'days'). format('DD-MM-YYYY'); //====> equivalent to from.setDate(from.getDate() + 1);
}
console.log(dates);

Related

How Can I use new Date().setHours(0,0,0) in a single line? [duplicate]

What is the simplest way to obtain an instance of new Date() but set the time at midnight?
The setHours method can take optional minutes, seconds and ms arguments, for example:
var d = new Date();
d.setHours(0,0,0,0);
That will set the time to 00:00:00.000 of your current timezone, if you want to work in UTC time, you can use the setUTCHours method.
Just wanted to clarify that the snippet from accepted answer gives the nearest midnight in the past:
var d = new Date();
d.setHours(0,0,0,0); // last midnight
If you want to get the nearest midnight in future, use the following code:
var d = new Date();
d.setHours(24,0,0,0); // next midnight
A one-liner for object configs:
new Date(new Date().setHours(0,0,0,0));
When creating an element:
dateFieldConfig = {
name: "mydate",
value: new Date(new Date().setHours(0, 0, 0, 0)),
}
Just going to add this here because I landed on this page looking for how to do this in moment.js and others may do too.
[Rationale: the word "moment" already appears elsewhere on this page so search engines direct here, and moment.js is widespread enough to warrant to being covered going on how often it is mentioned in other date-related SO questions]
So, in version 2.0.0 and above:
date.startOf('day');
For earlier versions:
date.sod();
Docs:
http://momentjs.com/docs/#/manipulating/start-of/
You can probably use
new Date().setUTCHours(0,0,0,0)
if you need the value only once.
If calculating with dates summertime will cause often 1 hour more or one hour less than midnight (CEST). This causes 1 day difference when dates return. So the dates have to round to the nearest midnight. So the code will be (thanks to jamisOn):
var d = new Date();
if(d.getHours() < 12) {
d.setHours(0,0,0,0); // previous midnight day
} else {
d.setHours(24,0,0,0); // next midnight day
}
Adding usefulness to #Dan's example, I had the need to find the next midday or midnight.
var d = new Date();
if(d.getHours() < 12) {
d.setHours(12,0,0,0); // next midnight/midday is midday
} else {
d.setHours(24,0,0,0); // next midnight/midday is midnight
}
This allowed me to set a frequency cap for an event, only allowing it to happen once in the morning and once in the afternoon for any visitor to my site. The date captured was used to set the expiration of the cookie.
I have made a couple prototypes to handle this for me.
// This is a safety check to make sure the prototype is not already defined.
Function.prototype.method = function (name, func) {
if (!this.prototype[name]) {
this.prototype[name] = func;
return this;
}
};
Date.method('endOfDay', function () {
var date = new Date(this);
date.setHours(23, 59, 59, 999);
return date;
});
Date.method('startOfDay', function () {
var date = new Date(this);
date.setHours(0, 0, 0, 0);
return date;
});
if you dont want the saftey check, then you can just use
Date.prototype.startOfDay = function(){
/*Method body here*/
};
Example usage:
var date = new Date($.now()); // $.now() requires jQuery
console.log('startOfDay: ' + date.startOfDay());
console.log('endOfDay: ' + date.endOfDay());
In case you already have d3.js as a dependency in your project, or don't mind bringing it in, d3-time (d3.js library is modular as of v4.0.0) has got Intervals.
They might prove useful when setting dates to "default" values, e.g. midnight, 0.00 seconds, the first of the month, etc.
var d = new Date(); // Wed Aug 02 2017 15:01:07 GMT+0200 (CEST)
d3.timeHour(d) // Wed Aug 02 2017 00:00:00 GMT+0200 (CEST)
d3.timeMonth(d) // Tue Aug 01 2017 00:00:00 GMT+0200 (CEST)
Using the dayjs library, you can use the startOf('day') method.
const dayjs = require('dayjs');
const todayAtMidnight= dayjs().startOf('day');
// Get as a native date object
console.log(todayAtMidnight.toDate());
To get the start of a particular day you can use the following:
const date = dayjs("2023-02-12").startOf('day');

What is the source of this off-by-one error in a JavaScript date?

I am trying to write a function that will take a string like 07/2020 and then return whether it is more than three months away.
I have written a function isMoreThan3MonthsHence that I am reasonably sure works correctly:
const isMoreThan3MonthsHence = ({ utcYear, utcMonth },
now = new Date,
target = new Date(Date.UTC(utcYear, utcMonth)),
threeMonthsAway = new Date(now.valueOf()).setUTCMonth(now.getUTCMonth() + 3)) =>
(target > threeMonthsAway)
console.log(isMoreThan3MonthsHence({ utcYear: 2020, utcMonth: 7 })) // true (correct!)
The problem comes when I try to construct a Date object to use to populate the arguments for isMoreThan3MonthsHence.
const validate = (str,
[localMonth, localYear] = str.split('/'),
date = new Date(+localYear, (+localMonth)-1)) =>
isMoreThan3MonthsHence({ utcYear: date.getUTCFullYear(), utcMonth: date.getUTCMonth() })
// Note: input is one-based months
console.log(validate('07/2020')) // false (but should be true!)
I think the reason is that new-ing up a Date in validate without specifying the timezone will use the local timezone in effect at the supplied date, which will be BST (UTC+1).
Wed Jul 01 2020 00:00:00 GMT+0100 (British Summer Time)
This time is actually 2300hrs on June 30th in UTC. So the month is actually 5 in zero-based terms. But I don't want this behavior. I want it so specifying July actually means July in UTC.
How can I fix this?
It looks like you're mixing the usage of Date.UTC and not when instantiating dates. For example, if you use the following for your validate function:
const validate = (str,
[month, year] = str.split('/'),
date = new Date(Date.UTC(+year, (+month)-1))) =>
isMoreThan3MonthsHence({ utcYear: date.getUTCFullYear(), utcMonth: date.getUTCMonth() })
// Note: input is one-based months
console.log(validate('07/2020')) // Now true
It works as expected: JSFiddle
Removing the usage of Date.UTC altogether would perform the calculation in the user's local timezone, with any applicable daylight saving adjustment included. This could be seen as a valid approach, however would result in the behaviour you have described.
Note I've renamed the local prefixed variables based on feedback from Bergi. Using Date.UTC implies you're passing in UTC arguments.
Other than mixing UTC and local dates, the way you're adding 3 months will cause an incorrect response for dates like 31 March, where adding 3 months simply by incrementing the month number results in a date for 1 July. See Adding months to a Date in JavaScript.
So validate('07,2020') will return false if run on 31 March.
To fix that, when adding months, check that the updated date is still on the same day in the month, otherwise it's rolled over so set it to the last day of the previous month.
function validate(s) {
let testDate = addMonths(new Date(), 3);
let [m, y] = s.split(/\D/);
return testDate < new Date(y, m-1);
};
function addMonths(date, months) {
let d = date.getDate();
date.setMonth(date.getMonth() + +months);
// If rolled over to next month, set to last day of previous month
if (date.getDate() != d) {
date.setDate(0);
}
return date;
}
// Sample
console.log('On ' + new Date().toDateString() + ':');
['07/2020', '04/2020'].forEach(
s => console.log(s + ' - ' + validate(s))
);

How to use Moment.js to determine financial quarters based upon Unix timestamp?

My goal is to compare two data sets representing financial quarters. The quirks of the API require two dates per get request: startDate (inclusive) and endDate (exclusive). Therefore, e.g., to get data for the month of July 2016 you need:
startDate = 20160701 and endDate = 20160801.
Given a single unixStartDate (which is a Unix timestamp), I want to find (1) the startDate and endDate for the current quarter, and (2) the startDate and endDate for the previous quarter.
So far I can use moment.js to determine the current quarter without issue:
const currentQuarter = moment.unix(unixStartDate).utc().quarter();
I could probably make a hacky switch-case based on that information, but I'm hoping for a more elegant solution.
Moment.js also contains moment().startOf('quarter'), but I've been unable to figure out its proper usage. Console.log yields an object:
Moment {_isAMomentObject: true,
_isUTC: false,
_pf: Object,
_locale: Locale,
_d: Fri Jul 01 2016 00:00:00 GMT-1000 (HST)…}
Here's a verbose version:
const moment = require('moment');
let unixStartDate = ...;
let current = moment(unixStartDate);
let currentStartOf = moment(current).startOf('quarter');
let currentEndOf = moment(current). endOf('quarter').add(1, 'day');
let previous = moment(current).subtract(1, 'quarter');
let previousStartOf = moment(previous).startOf('quarter');
let previousEndOf = moment(previous). endOf('quarter').add(1, 'day');
console.log(
'current ',
currentStartOf.format('YYYYMMDD'),
currentEndOf .format('YYYYMMDD')
)
console.log(
'previous',
previousStartOf.format('YYYYMMDD'),
previousEndOf .format('YYYYMMDD')
)

Comparing two dates in different timezones

I'm comparing two dates; one returned as a UTC String (as part of an Ajax response) and the second in local browser time:
Basically, I want to see if the date returned (endTime) happened before right now. My code is below and I thought I had it right but it's not working.
var isActive = true;
var buffer = 30000; // 30 seconds
var endTime = new Date(Date.parse(response.endTime)); // Fri Oct 23 2015 12:01:14 GMT-0400 (EDT)
var now = new Date(); // Thu Oct 22 2015 20:01:31 GMT-0400 (EDT)
var nowUtc = new Date(now).toUTCString(); // "Fri, 23 Oct 2015 00:01:31 GMT"
var nowTimeMs = new Date(nowUtc).getTime(); // 1445558491000
var endTimeMs = endTime.getTime() + buffer; // 1445616104000
if( nowTimeMs > endTimeMs ){
isActive = false;
}
isActive should remain as true but instead it's false. I feel like I've been looking at this too long and am missing something very simple. Am I?
Thanks for any helpful tips.
Update:
Based on the responses I thought I'd update my question. What is the best way to compare two dates where one is this:
new Date(); // Thu Oct 22 2015 21:51:53 GMT-0400 (EDT)
...and the other is a String representation of date:
"2015-10-23 01:49:27"
I figure the best way to create a valid Date object out of the String is using this code.
isThisActive:function(p){
var isActive = true;
var buffer = 30000;
var pEndTime = myObj.parseStringAsDate(p.callEndTime);
var now = new Date();
var offset = now.getTimezoneOffset() * 60000;
now.setTime( now.getTime() + offset );
var nowTimeMs = now.getTime();
var endTimeMs = pEndTime.getTime() + buffer;
if( nowTimeMs > endTimeMs ){
isActive = false;
}
return isActive;
},
parseStringAsDate:function(str){
var dateTimeStr = str.split(" ");
var dateStr = dateTimeStr[0].split("-");
var year = dateStr[0];
var month = dateStr[1];
var day = dateStr[2];
var timeStr = dateTimeStr[1].split(":");
var hours = timeStr[0];
var minutes = timeStr[1];
var seconds = timeStr[2];
return new Date( year,month,day,hours,minutes,seconds);
}
Because "pEndTime" is in UTC I applied the offset to the "now" Date object but even this is not working. Where's the problem here? I thought this would solve it.
SOLVED:
The latest code I posted did work. I was just getting incorrect values for the response.endTime (It wasn't converted to correct military time). Thank you everyone for your input. I've tried to upgrade as many helpful responses as I could.
You should not use the Date constructor or Date.parse (which do the same thing) to parse date strings. Either write your own parse function (below) or use a well maintained library.
To parse the format in the OP, you can use:
// Parse Thu Oct 22 2015 20:01:31 GMT-0400 (EDT)
function parseMMMDY(s) {
var b = s.split(/\W/);
var months = {jan:0,feb:1,mar:2,apr:3,may:4,jun:5,jul:6,aug:7,sep:8,oct:9,nov:10,dec:11};
var sign = /GMT-\d{4}/i.test(s)? 1 : -1;
var min = +b[5] + (sign * b[8].slice(0,2) * 60 ) + (sign * b[8].slice(-2));
return new Date(Date.UTC(b[3], months[b[1].toLowerCase().slice(0,3)], b[2], b[4], min, b[6]));
}
document.write(parseMMMDY('Thu Oct 22 2015 20:01:31 GMT-0400 (EDT)'));
I think the problem is here:
var endTime = new Date(Date.parse(response.endTime));
respnonse.endTime is UTC, right? But when you parse it to Date value, Date.parse assumes it is in local timezone (GMT-0400 as in your example code). It means that the endDate gets the wrong value
I usually use moment.js in my projects which related to formatting date time, especially in the reports (I'm working in the field of finance). You must have one more library in your project but it provides many other functionalities
Sorry, this is for your new update. I haven't got enough 'population' to leave a comment :P
var endTime = new Date(Date.parse(response.endTime)); // Fri Oct 23 2015 12:01:14 GMT-0400 (EDT)
var now = new Date(); // Thu Oct 22 2015 20:01:31 GMT-0400 (EDT)
Your endTime doesn't seem to return a UTC date as you mentioned. It looks to be using (EDT) so maybe you didn't have to convert it to UTC.

Javascript dates to array

I am working on a website where users can book vacation houses.
Booking always is from Saturday to Saturday. If users click on any date in that week, the whole week must be selected. I've got that part working.
I gave the selected dates the "date-range-selected" class. And if the first day was already booked, but only in the morning then I gave it the class "half-selected". Now because I need to calculate with these dates I need the full dates and not only the day number. Only the ones with the "half-selected" and the "date-range-selected" need to be added to an array.
I tried to push variables to that array and it seemed to work, but when I try to use the array after the for loop, it seems like all array content is changed to the date after the last selected date, what am I doing wrong?
//ex.selected dates are: 9-16 august
var halfke= document.getElementsByClassName("half-selected");
var rest = document.getElementsByClassName("date-range-selected");
var dagen = [];
if(typeof halfke[0] != 'undefined')
{
dagen.push(halfke[0].getElementsByClassName("ui-state-default")[0].innerHTML.trim());
}
for (index = 0; index < rest.length; ++index)
{
dagen.push(rest[index].getElementsByClassName("ui-state-default")[0].innerHTML.trim());
}
var myDate = new Date(cur);
var dw = myDate.getDay();
myDate.setDate(myDate.getDate() - (dw+1));
var myDate2 = new Date(cur);
myDate2.setDate(myDate2.getDate() + (6-dw));
//alert(dagen[0]);
var juiste_dagen = new Array();
for (var d = myDate; d <= myDate2; d.setDate(d.getDate() + 1)) {
//alert(dagen[0]+ " - " + String(d.getDate()))
if(dagen.indexOf(String(d.getDate()))>-1)
{
//alert(d);
juiste_dagen.push(d);
alert(juiste_dagen[0]);//here it alerts 9 august (as it should)
}
}
alert("done");
alert(juiste_dagen[0]);//here it alerts 17 august(which it should not do)
alert(juiste_dagen[1]);//here it alerts 17 august(which it should not do)
alert(juiste_dagen[2]);//here it alerts 17 august(which it should not do)
It's probably something small and stupid but I can't seem to find it.
Any help is appreciated!
This is a pass-by-reference problem and it's working as intended. Primitives (like numbers and strings) are pass-by-value. Date is an object, and it is passed by reference, which means without copying. Everywhere you pass a Date object is the same date. Look at the following code, tested just now in my Chrome console:
var myDate = new Date();
undefined
var juiste_dagen = [];
undefined
juiste_dagen.push(myDate);
1
juiste_dagen[0];
Fri Jul 18 2014 09:35:02 GMT-0500 (CDT)
myDate.setDate(19);
1405780502241
juiste_dagen[0];
Sat Jul 19 2014 09:35:02 GMT-0500 (CDT)
So the problem is in your last forloop where you do d.setDate(d.getDate() + 1). You're essentially setting all of the dates pushed into your array to this date on every iteration of the loop. Either use a simple number for a loop counter or create a new date each time before changing the day.

Categories