Related
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);
I've got a simple problem, but I'm struggling to find the easiest solution without transforming the array a hundred times.
I want to do a simple stacked graph in google sheets, with weeks on X and values on Y. I got the values for each week, but only for weeks, that have a value.
The values are all calculations I've done with google apps script/ js.
person1 = [[2019/37,2], [2019/42,3]] and so on, for multiple persons and for 80 weeks in total.
The num value is the total value after each week. So I want the array to be filled up with the missing weeks. Therefore I mapped this to another array, where I have all the weeks but no values, giving these weeks the value 0:
person1= [[2019/37,2],[2019/38,0],[2019/39,0],...,[2019/42,3],[2019/43,0],[2019/44,0],...]
This of course does not fit to see a progress in the graph.
So I need something to set the weeks, which were filled up, to the previous value, resulting in
person1= [[2019/37,2],[2019/38,2],[2019/39,2],...,[2019/42,3],[2019/43,3],[2019/44,3],...]
Looping through this and setting the values with something like person[i][1] == person[i-1][1] seems not to be a good practice of course.
So, what would be the best way to achieve this? I'm kind of stuck with this now, I feel like I don't see the forest for the trees.
Thanks in advance!
code:
let valueArray = [[2019/37,2], [2019/42,3]]
let weeksArray = [2019/38,2019/39,2019/40,2019/41...]
//find missing weeks
let notFound = weeksArray.filter(el => valueArray.includes(el) == false).map(x => [x,0]);
//concat and sort
let outArray = arr.concat(notFound).sort((a,b)=> a[0].localeCompare(b[0]));
//output:
//[[2019/37,2],[2019/38,0],[2019/39,0],...,[2019/42,3],[2019/43,0],[2019/44,0],...]
Solution:
Since you already have the expanded array, you can use map on the whole array and use a function to replace the values:
var weeks = [[2019/37,2],[2019/38,0],[2019/39,0],[2019/40,3],[2019/41,0],[2019/42,4],[2019/43,0],[2019/44,0]];
weeks.map((a,b)=>{weeks[b][1] = (a[1] == 0 && b > 0) ? weeks[b-1][1] : weeks[b][1]});
To make it more readable, this is the same as:
weeks.forEach(function missing(item,index,arr) {
if (item[1] == 0 && index > 0) {
arr[index][1] = arr[index-1][1];
}
}
);
Console log:
References:
Arrow Functions
Conditional Operator
Array.prototype.map()
function fixArray() {
var array = [["2019/1", "1"], ["2019/10", "2"], ["2019/20", "3"], ["2019/30", "4"], ["2019/40", "5"]];
var oA = [];
array.forEach(function (r, i) {
oA.push(r);
let t1 = r[0].split('/');
let diff;
if (i + 1 < array.length) {
let inc = 1;
let t2 = array[i + 1][0].split('/');
if (t1[0] == t2[0] && t2[1] - t1[1] > 1) {
do {
let t3 = ['', ''];
t3[0] = t1[0] + '/' + Number(parseInt(t1[1]) + inc);
t3[1] = r[1];
diff = t2[1] - t1[1] - inc;
oA.push(t3);
inc++;
} while (diff > 1);
}
}
});
let end = "is near";
console.log(JSON.stringify(oA));
}
console.log:
[["2019/1","1"],["2019/2","1"],["2019/3","1"],["2019/4","1"],["2019/5","1"],["2019/6","1"],["2019/7","1"],["2019/8","1"],["2019/9","1"],["2019/10","2"],["2019/11","2"],["2019/12","2"],["2019/13","2"],["2019/14","2"],["2019/15","2"],["2019/16","2"],["2019/17","2"],["2019/18","2"],["2019/19","2"],["2019/20","3"],["2019/21","3"],["2019/22","3"],["2019/23","3"],["2019/24","3"],["2019/25","3"],["2019/26","3"],["2019/27","3"],["2019/28","3"],["2019/29","3"],["2019/30","4"],["2019/31","4"],["2019/32","4"],["2019/33","4"],["2019/34","4"],["2019/35","4"],["2019/36","4"],["2019/37","4"],["2019/38","4"],["2019/39","4"],["2019/40","5"]]
I have an array of strings that represents time slots of some events
const times = [ ['12:15', '14:00'], ['09:00', '10:00'], ['10:30', '12:00'] ]
They are not sorted and they have been all converted to 24 Hour Clock.
I am trying to come up with a function to sort it.
here is my attempt:
function isT1EarlierThanT2(t1, t2) {
const [h1, m1] = t1.split(':')
const [h2, m2] = t2.split(':')
if(h1 !== h2) {
return h1 < h2
} else {
return m1 < m2
}
}
const times = [ ['12:15', '14:00'], ['09:00', '10:00'], ['10:30', '12:00'] ]
times.sort(([startTimeA], [startTimeB]) => isT1EarlierThanT2(...[startTimeA, startTimeB]) )
but it seems like it is not sorting it correctly. I can't seem to find the reason. Can anyone help?
You can just compare strings since it's 24 hour clock and you are padding with 0.
This will order by the first time in each sub array.
const times = [ ['12:15', '14:00'], ['09:00', '10:00'], ['10:30', '12:00'] ]
times.sort(([a], [b]) => a < b ? -1 : 1);
console.log(times)
No need to split the strings. You can just rely on lexical comparison in this case:
const times = [['12:15', '14:00'], ['09:00', '10:00'], ['10:30', '12:00']];
times.sort(([t1], [t2]) => t1 > t2 ? 1 : t2 > t1 ? -1 : 0);
console.log(times);
For example, take the time range from 05/10/2019 to 05/25/2019.
Dates on this interval need to be aggregated like this (2019 is omitted for brevity):
const result = [
[ '05-10', '05-11', '05-12'], // week 1
['05-13', '05-14', '05-15', '05-16', '05-17', '05-18', '05-19'], // week 2
['05-20', '05-21', '05-22', '05-23', '05-24', '05-25' ], // week 3
];
What is the best way to solve this problem with JS?
Is it possible to implement this by setting the beginning of the week on any day?
Will packages moment and moment-range help in this?
You can go through the dates, and if the day is 1 (Monday), create a new Array in your results:
const startDate = new Date('05-10-2019'),
endDate = new Date('05-25-2019'),
result = [];
function _twoDigits(x) {
return String(x).padStart(2, '0');
}
let tmp = startDate;
do {
if (tmp.getDay() === 1 || result.length === 0) {
// Create a week Array
result.push([]);
}
const str = `${_twoDigits(tmp.getMonth() + 1)}-${_twoDigits(tmp.getDate())}`;
// Add this date to the last week Array
result[result.length - 1].push(str);
// Add 24 hours
tmp = new Date(tmp.getTime() + 86400000);
} while (tmp.getTime() <= endDate.getTime());
console.log(result);
Note: MomentJS may help, but it's a big library. If you only need to do 2 or 3 basic things with dates, I would recommend not using it. If you need to do a lot of work with dates, then yes, it's a powerful library that will save you a lot of time.
Here is one possible implementation if you are interested in moment.js code.
But as blex said it's a large lib.
const start = moment('2019-05-10');
const end = moment('2019-05-25');
const array = [[]];
const from_date = moment(start).startOf('isoWeek');
const to_date = moment(end).endOf('isoWeek');
let j = 0;
let added = 0;
for (let currentDate = moment(from_date); currentDate <= to_date; currentDate.add(1, 'day')) {
if (added === 7) {
array.push([]);
j++;
added = 0;
}
if (currentDate.isBetween(start, end, null, '[]')) {
array[j].push(currentDate.format('MM-DD'));
}
else {
array[j].push('');
}
added++;
}
document.getElementById('output').innerText = JSON.stringify(array);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
<p id="output"></p>
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