I've implemented format function to convert input hours 9a-7p to 9:00am - 7:00pm or 9:30a-7p to 9:30am to 7:30pm.
the inputs looks like {sun: 9a-7p, mon: 8:30a - 7p}
Is there more generic way how to handle it this case, cuz this one does not cover pm - am or am - am or pm - pm
const formattedHours = (days: {[day: string]: string}) => {
return Object.keys(days).map((day) => {
const [openHours, closedHours] = days[day].replaceAll('a', '').replaceAll('p', '').split('-');
const [openHour, openMinute = '00'] = openHours.split(':');
const [closedHour, closedMinute = '00'] = closedHours.split(':');
return `${openHour}:${openMinute}am - ${closedHour}:${closedMinute}pm`;
});
};
Here's one way of going about it. By checking for the presence of 'a' or 'p' we can store the meridian and insert it after scrubbing the element of text, extra spaces and making sure it has minutes included. I used Object.entries since it allows me to iterate the object and modify the value in question in the same loop. I surround the whole thing with Object.fromEntries to convert it back into an object.
let data = {
sun: "9a-7p",
mon: "8:30a - 7p",
tues: "9pm - 11pm",
weds: "7am - 11:45 am",
thurs: "11a-12:00pm"
};
const formattedHours = days => {
return Object.fromEntries(Object.entries(days).map((day) => {
day[1] = day[1].split("-").map(t => {
let merid = t.includes('a') ? 'am' : 'pm';
t = t.replaceAll(/[a-zA-Z]/g, '').trim()
if (!t.includes(':')) t = `${t}:00`;
t += merid
return t
}).join("-");
return day
}));
};
let newdata = formattedHours(data);
console.log(newdata)
Many ways to go about it, but fundamentally it's about converting the time to a different format. The following also pads the hour with spaces to help with alignment, missing minutes are treated as "0" and single digit minutes are padded with a leading zero.
let fixTime = time => {
let range = time.split(/\s*-\s*/).map(time => {
let [h,m] = time.match(/\d+/g);
return `${h.padStart(2, ' ')}:${(m || '0').padStart(2, '0')}${/a/.test(time)? 'am' : 'pm'}`;
});
return range.join(' to ');
};
let data = {
sun: '9a-7p',
mon: '8:30a - 7p',
tue: '10:31a -1:5p',
wed: '1:31p-11:5p',
};
let result = Object.keys(data).reduce((acc, day) => {
acc[day] = fixTime(data[day]);
return acc;
}, Object.create(null));
console.log(result);
Related
I'm trying to create a dynamic calendar.
I have opening and closing hours.
I would like to make a loop to get all the half hour slots between these two times.
The goal is to create a div that contains the list of these schedules.
do you have a method, an idea please to help me achieve this?
My data, inside a json document, looks like this:
"minMax": {
"min": "07:00",
"max": "18:00"
}
(I have already retrieved the day) my goal is to display the schedules.
const minHour = week[weekNumber].minMax.min // => 07:00
const maxHour = week[weekNumber].minMax.max // => 18:00
The problem is that I don't know how to split the schedules by half hour between the min hour and the max hour.
Based on the information you gave I came up with this solution to generate half hour slots. If there is any corrections needed to be made or edge cases to handle, please let me know.
function halfHourSlots(min, max) {
let slots = [];
let [minHour] = min.split(':');
let [maxHour] = max.split(':');
minHour = Number(minHour);
maxHour = Number(maxHour);
while (minHour != maxHour) {
let halfHourString = `${minHour}:30`.padStart(
2,
'0'
);
minHour += 1;
let hourString = `${
minHour == 24 ? '00' : minHour
}:00`.padStart(2, '0');
slots.push(halfHourString);
slots.push(hourString);
}
return slots;
}
console.log(halfHourSlots('07:00', '18:00'));
You can try this,
var convertValue = time => ((hour,min) => hour*2 + min/30)(...time.split(':').map(parseFloat)),
toTime = int => [Math.floor(int/2), int%2 ? '30' : '00'].join(':'),
range = (from, to) => Array(to-from+1).fill().map((_,i) => from + i),
halfHourInterval = (t1, t2) => range(...[t1, t2].map(convertValue)).map(toTime);
console.log(halfHourInterval('07:00', '15:30'));
Goodnight. I'm trying to capture every Friday, Sunday and Wednesday between two dates using Moment.js. I couldn't understand why it doesn't capture the days:
2021-12-08
2021-12-10
I managed to get this far:
const allDays = [0, 3, 5];
function formatToPush(dt_inicio, dt_final, dia, horas) {
let start = moment(dt_inicio);
let end = moment(dt_final);
let result = [];
let datas = [];
let current = start.clone();
if ((current.day(dia).isSameOrAfter(start)) || (current.day(dia).isSameOrAfter(end)) || (current.day(7 + dia).isSameOrBefore(end))) {
result.push(current.clone());
}
result.map(m => {
horas.map(h => {
m.set({ hour: h.split(':')[0], minute: h.split(':')[1], second: 0, millisecond: 0 });
datas.push(m.format('YYYY-MM-DD HH:mm:ss'))
})
});
return datas;
}
let final = [];
for (let i in allDays) {
final.push(...formatToPush('2021-12-01', '2021-12-10', allDays[i], ["10:00", "16:00", "22:30"]))
}
console.log(final)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
Can anyone help me find the error?
Thanks!
The condition for whether the day is within the bounds is always true for either or both of the first two clauses. This means the whole expression is true without evaluating the third clause which is the one that could set the date to one of the later dates you are missing. Since you aren't running that if-statement in a loop, it will only ever push one date to the result array.
A more generalized algorithm would use a loop.
let current = start.clone();
if (current.day(dia).isSameOrAfter(start) && current.isSameOrBefore(end)) {
result.push(current.clone());
}
while (current.day(7 + dia).isSameOrAfter(start) && current.isSameOrBefore(end)) {
result.push(current.clone());
}
Note: I also changed the conjunction to && because with the loop, isAfter would always be true. Also I omitted current.day(dia) in the second clause since the first one is already setting the day of the week for current.
You push a day (result.push(current.clone())) exactly once, so you can't expect to have more than one date per day. You can make a second if statement with this condition: (current.day(7 + dia).isSameOrBefore(end)) and push it second time.
Also, in js, when first condition in if is met, the other are not resolved.
const allDays = [0, 3, 5];
function formatToPush(dt_inicio, dt_final, dia, horas) {
let start = moment(dt_inicio);
let end = moment(dt_final);
let result = [];
let datas = [];
let current = start.clone();
if ((current.day(dia).isSameOrAfter(start)) || (current.day(dia).isSameOrAfter(end)) || (current.day(7 + dia).isSameOrBefore(end))) {
result.push(current.clone());
}
if (current.day(7 + dia).isSameOrBefore(end)) {
result.push(current.clone());
}
result.map(m => {
horas.map(h => {
m.set({ hour: h.split(':')[0], minute: h.split(':')[1], second: 0, millisecond: 0 });
datas.push(m.format('YYYY-MM-DD HH:mm:ss'))
})
});
return datas;
}
let final = [];
for (let i in allDays) {
final.push(...formatToPush('2021-12-01', '2021-12-10', allDays[i], ["10:00", "16:00", "22:30"]))
}
console.log(final)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
Here is an alternative if want to do this in three easy lines using the Date API.
I offer this alternative considering momentjs is 17,000+ lines of code in 42k.
For your consideration.
While processing the range from start to finish (inclusive)
convert the date to a dateString and look for Fri or Sun or Wed
if found -> Add the date to the result array
check the next date
const startDate = new Date('2021', '11', '1');
const endDate = new Date('2021', '11', '31');
var tempDate = new Date('2021', '11', '1');
var result = [];
while (tempDate.valueOf() !== endDate.valueOf()) {
if (/Fri|Sun|Wed/.test(tempDate.toDateString())) result.push(new Date(tempDate));
tempDate.setDate(tempDate.getDate() + 1);
}
result.forEach(day => console.log(day.toDateString(), day.toISOString().replace("T",' ')));
I am making a dynamic portfolio for myself using VueJS.
I created a way to update experiences and order it based on currently ongoing jobs showing first sorted in ascending order meaning a job with start date May 2021 will show first and then March 2021 (both being present).
Next, if I set an end date for the job, it should update and place the current jobs in the front which isn't happening.
Algorithm:
newExp() {
this.editableExperience.sort((a,b) => {
a = a.period.split(' - ');
b = b.period.split(' - ');
let aStartDate = a[0];
let aEndDate = a[1];
let bStartDate = b[0];
let bEndDate = b[1];
if (aEndDate == 'Present' && bEndDate == 'Present') {
return new Date(bStartDate) - new Date(aStartDate);
} else if (aEndDate == 'Present') {
return a;
} else if (bEndDate == 'Present') {
return b;
} else {
return new Date(bStartDate) - new Date(aStartDate);
}
})
this.experience = this.editableExperience;
}
editableExperience is an array of experiences: (I have added only required information)
editableExperience = [{period: 'May 2021 - Present'}, {period: 'November 2020 - Present'}, {period: 'January 2021 - March 2021'}, {period: 'March 2018 - July 2020'}]
Exact issue situation:
Setting the third element to present job brings it to position 2 but giving it an end date again does not send it to position 3 again.
Setting the last element to present does not bring it in front of the non-present jobs.
Your compare function is returning a string or a number while the compare function should return either 1, 0 or -1 as per the MDN docs.
I have made changes to your code below:
newExp() {
this.editableExperience.sort((a,b) => {
a = a.period.split(' - ');
b = b.period.split(' - ');
let aStartDate = a[0];
let aEndDate = a[1];
let bStartDate = b[0];
let bEndDate = b[1];
if (aEndDate == 'Present' && bEndDate == 'Present') {
return (new Date(bStartDate) - new Date(aStartDate)) > 1 ? 1 : -1;
} else if (aEndDate == 'Present') {
return -1;
} else if (bEndDate == 'Present') {
return 1;
} else {
return (new Date(bStartDate) - new Date(aStartDate)) > 1 ? 1 : -1;
}
});
this.experience = this.editableExperience;
}
The view model is a little bit mixed with data model, I would suggest to keep a clean data model which hold the original values, it is good for processing like sort. then a a computed property as view model which is depend on the data model.
data: () => ({
editableExperience: [
{start: 202105, end: 999999},
{start: 202011, end: 999999},
{start: 202101, end: 202103},
{start: 201803, end: 202107},
],
}),
then the sorting will looks like:
this.editableExperience.sort((a,b) => {
return b['end'] === a['end']? b['start'] - a['start'] : b['end'] - a['end']
})
for your view(display)
computed: {
viewExperiences() {
const ve = []
for(const e of this.editableExperience) {
ve.push(this.getExperienceDisplay(e))
}
return ve
}
},
methods: {
formatExperienceDate(dateInt) {
if(dateInt === 999999) return 'Present'
const dateStr = dateInt.toString()
const date = new Date(dateStr.substring(0, 4) + '-' + dateStr.substring(4, 6))
return date.toLocaleDateString("en-US", {year: 'numeric', month: 'long'})
},
getExperienceDisplay(exp) {
const startDate = this.formatExperienceDate(exp['start'])
const endDate = this.formatExperienceDate(exp['end'])
return `${startDate} - ${endDate}`
},
}
So I have a timezone list and has a type of the following:
type TimezoneListType = {
label: string;
name: string;
offset: number;
lower?: string;
offsetString?: string;
};
const TIMEZONE_LIST: TimezoneListType[] = [
{
label: 'Niue',
name: 'Pacific/Niue',
offset: -11,
},
{
label: 'Pago Pago',
name: 'Pacific/Pago_Pago',
offset: -11,
},
//... and so on
];
const TIMEZONE_NAME_MAP: any = {};
const TIMEZONE_MAP = TIMEZONE_LIST.map((item) => {
const positive = item.offset >= 0;
const hour = item.offset | 0;
const minute = (item.offset - hour) * 60;
return TIMEZONE_NAME_MAP[item.name] = { // typescript is screaming in this line. if try to change the `any` to `TimezoneListType`
...item,
lower: item.label.toLowerCase(),
offsetString: 'something',
};
});
on the lower end of the code you'll see that I'm transforming timezone list to have keys of item.name then adding some property lower and offsetString.
My problem is item.name is giving me:
Element implicitly has an 'any' type because index expression is not of type number ts(7015)
on both item and name when I hover at them. And I'm not sure how to type it correctly.
EDIT: Ideal result of the transformation is the photo below:
I've just put any on the map argument like TIMEZONE_LIST.map((item: any)
Update: I've changed my object declaration type to
const TIMEZONE_NAME_MAP: Record<string, TimezoneListType> = {};
seems to work but I don't understand why? and Now the other part of my program is screaming as I've exported it to other places so I can use it like TIMEZONE_NAME_MAP.filter( ..so on) but does't recognize indexOf as I'm trying to use that inside the filter function. what do i do?
The assignment on the last line implies a for loop may be better suited to this scenario than a map. The purpose of the map is to create an array mapped to new values corresponding to the original, whereas your goal here is to create a dictionary.
PS, you also don’t seem to be using the values that you create during the mapping anywhere after assigning them (positive, minute)
.map operator returns one single object, no need to assign
const TIMEZONE_NAME_MAP: TimezoneListType[] = TIMEZONE_LIST.map((item) => {
const positive = item.offset >= 0;
const hour = item.offset | 0;
const minute = (item.offset - hour) * 60;
return {
...item,
lower: item.label.toLowerCase(),
offsetString: 'something',
};
});
Your getting that error because you are assigning an object to the array index and it is expecting you to an numeric index.
const TIMEZONE_MAP = TIMEZONE_LIST.map((item) => {
const positive = item.offset >= 0;
const hour = item.offset | 0;
const minute = (item.offset - hour) * 60;
return TIMEZONE_NAME_MAP[index] = { // typescript is screaming in this line.
...item,
lower: item.label.toLowerCase(),
offsetString: 'something',
};
});
EDIT: this would be a better way:
const TIMEZONE_MAP = TIMEZONE_LIST.map((item, index) => {
const positive = item.offset >= 0;
const hour = item.offset | 0;
const minute = (item.offset - hour) * 60;
return { // typescript is screaming in this line.
...item,
lower: item.label.toLowerCase(),
offsetString: 'something',
};
});
Edit 2 as per new requirement:
const TIMEZONE_MAP = TIMEZONE_LIST.map((item, index) => {
const positive = item.offset >= 0;
const hour = item.offset | 0;
const minute = (item.offset - hour) * 60;
let obj = {};
obj[item.name] = {...item,
lower: item.label.toLowerCase(),
offsetString: 'something'};
return obj;
});
I have this function:
function getInfoSchoolTime() {
var date = new Date();
var schoolBellTime = ["8:10","9:02","9:54","9:59","10:51","11:43","11:58","12:48","13:35","13:40","14:10","15:02","15:54"];
var remainingTime, currentHour;
for (var i = 0; i < schoolBellTime.length-1; i++) {
var startTime = schoolBellTime[i].split(":");
var endTime = schoolBellTime[i+1].split(":");
if (parseInt(startTime[0]) >= date.getHours() && parseInt(startTime[1]) >= date.getMinutes())
if (parseInt(endTime[0]) <= date.getHours() && parseInt(endTime[1]) <= date.getMinutes()) {
currentHour = i;
remainingTime=(parseInt(endTime[1])-date.getMinutes()+60)%60;
break;
}
}
if (currentHour == undefined)
return {current: -1, remaining: "not available"};
return {current: currentHour, remaining: remainingTime};
}
var info = getInfoSchoolTime();
console.log(info.current, info.remaining);
I have the schoolBellTime array that contains the timestamps of my school bell (I know, my school has strange bell times, these timestamps includes playtimes and lunchtime), this function is meant to return the 1st hour/2nd hour/3rd hour ... and the minutes that remains to the next hour/breaktime.
I checked all the code and can't find the error, it keeps returning {current: -1, remaining: "not available"}
The function at the top: setDateTime() takes a date and a time, and constructs a date object for that time.
Then I updated your function, I convert start and end to times on the current day, and then check if date.getTime() occurs between them. Then I simply subtract date.getTime() from end, and convert the result to minutes from milliseconds.
var setDateTime = function(date, str) {
var sp = str.split(':');
date.setHours(parseInt(sp[0], 10));
date.setMinutes(parseInt(sp[1], 10));
return date;
}
function getInfoSchoolTime() {
var date = new Date();
var schoolBellTime = ["8:10", "9:02", "9:54", "9:59", "10:51", "11:43", "11:58", "12:48", "13:35", "13:40", "14:10", "14:10", "15:02", "15:54"];
var remainingTime, currentHour, currentPeriod;
for (var i = 0; i < schoolBellTime.length - 1; i++) {
start = setDateTime(new Date(), schoolBellTime[i])
end = setDateTime(new Date(), schoolBellTime[i + 1])
if (date.getTime() > start.getTime() && date.getTime() < end.getTime()) {
currentHour = i
remainingTime = end.getTime() - date.getTime()
currentPeriod = ([schoolBellTime[i], schoolBellTime[i+1]]).join('-')
}
}
return {current: currentHour, currentPeriod: currentPeriod, remaining: Math.round(remainingTime * 0.0000166667)}
}
console.log(getInfoSchoolTime())
Here's a somewhat different approach, both to the code and the API. It uses two helper functions. Each should be obvious with a single example: pad(7) //=> "07" and pairs(['foo', 'bar', 'baz', 'qux']) //=> [['foo', 'bar'], ['bar', 'baz'], ['baz', 'qux']].
The main function takes a list of bell times and returns a function which itself accepts a date object and returns the sort of output you're looking for (period, remaining time in period.) This API makes it much easier to test.
const pad = nbr => ('00' + nbr).slice(-2)
const pairs = vals => vals.reduce((res, val, idx) => idx < 1 ? res : res.concat([[vals[idx - 1], val]]), [])
const schoolPeriods = (schoolBellTime) => {
const subtractTimes = (t1, t2) => 60 * t1.hour + t1.minute - (60 * t2.hour + t2.minute)
const periods = pairs(schoolBellTime.map(time => ({hour: time.split(':')[0], minute: +time.split(':')[1]})))
return date => {
const current = {hour: date.getHours(), minute: date.getMinutes()}
if (subtractTimes(current, periods[0][0]) < 0) {
return {message: 'before school day'}
}
if (subtractTimes(current, periods[periods.length - 1][1]) > 0) {
return {message: 'after school day'}
}
const idx = periods.findIndex(period => subtractTimes(current, period[0]) >= 0 && subtractTimes(period[1], current) > 0)
const period = periods[idx]
return {
current: idx + 1,
currentPeriod: `${period[0].hour}:${pad(period[0].minute)} - ${period[1].hour}:${pad(period[1].minute)}`,
remaining: subtractTimes(period[1], current)
}
}
}
const getPeriod = schoolPeriods(["8:10","9:02","9:54","9:59","10:51","11:43","11:58","12:48","13:35","13:40","14:10","14:10","15:02","15:54"])
console.log("Using current time")
console.log(getPeriod(new Date()))
console.log("Using a fixed time")
console.log(getPeriod(new Date(2017, 11, 22, 14, 27))) // will Christmas break ever come?!
I made a random guess at the behavior you would want if the date is outside the period range.
Internally, it creates a list of period objects that look like
[{hour:9, minute: 59}, {hour: 10, minute: 51}]
Perhaps it would be cleaner if instead of a two-element array it was an object with start and end properties. That would be an easy change.
Do note that for this to make sense, the bells need to be listed in order. We could fix this with a sort call, but I don't see a good reason to do so.
Here is an ES6 example using deconstruct (const [a,b]=[1,2]), array map, array reduce, partial application (closure) and fat arrow function syntax.
This may not work in older browsers.
//pass date and bellTimes to function so you can test it more easily
// you can partially apply bellTimes
const getInfoSchoolTime = bellTimes => {
//convert hour and minute to a number
const convertedBellTimes = bellTimes
.map(bellTime=>bellTime.split(":"))//split hour and minute
.map(([hour,minute])=>[new Number(hour),new Number(minute)])//convert to number
.map(([hour,minute])=>(hour*60)+minute)//create single number (hour*60)+minutes
.reduce(//zip with next
(ret,item,index,all)=>
(index!==all.length-1)//do not do last one, create [1,2][2,3][3,4]...
? ret.concat([[item,all[index+1]]])
: ret,
[]
);
return date =>{
//convert passed in date to a number (hour*60)+minutes
const passedInTime = (date.getHours()*60)+date.getMinutes();
return convertedBellTimes.reduce(
([ret,goOn],[low,high],index,all)=>
//if goOn is true and passedInTime between current and next bell item
(goOn && passedInTime<high && passedInTime>=low)
? [//found the item, return object and set goOn to false
{
current: index+1,
currentPeriod: bellTimes[index]+"-"+bellTimes[index+1],
remaining: high-passedInTime
},
false//set goOn to false, do not continue checking
]
: [ret,goOn],//continue looking or continue skipping (if goOn is false)
[
{current: 0, currentPeriod: "School is out", remaining: 0},//default value
true//initial value for goOn
]
)[0];//reduced to multiple values (value, go on) only need value
}
};
//some tests
const date = new Date();
//partially apply with some bell times
const schoolTime = getInfoSchoolTime(
[
"8:10", "9:02", "9:54", "9:59", "10:51",
"11:43", "11:58", "12:48", "13:35", "13:40",
"14:10", "14:10", "15:02", "15:54"
]
);
//helper to log time from a date
const formatTime = date =>
("0"+date.getHours()).slice(-2)+":"+("0"+date.getMinutes()).slice(-2);
date.setHours(11);
date.setMinutes(1);
console.log(formatTime(date),schoolTime(date));//11:01
date.setHours(15);
date.setMinutes(53);
console.log(formatTime(date),schoolTime(date));//15:53
date.setHours(23);
date.setMinutes(1);
console.log(formatTime(date),schoolTime(date));//23:01