skip to Main Content

i have a list of opening hours that more or less are in this kind of format, one value for one entire day:

[
   "",
   "Closed",
   "8:30 AM–7:30 PM",
   "9 AM–12:30 PM, 2:30–7:30 PM",
   "2:30–7:30 PM",
   "3–7 PM",
   "10 AM–7 PM",
   "8 AM–1 PM, 2:30–7 PM",
   "8 AM–1 PM, 2–7 PM",
   "8 AM–8:30 PM",
   "9 AM–12 PM, 2:30–7 PM",
   "Open 24 hours",
]

of course the time value can change, but i need to convert those in morning and afternoon opening, so i need to convert those string in a time element i think and then using 13:00 (1 PM) as the end of morning generating for each line something like this:

[
   {morning:"",afternoon:""},
   {morning:"closed",afternoon:"closed"},
   {morning:"8-13",afternoon:"13-19"},
   {morning:"9-12:30",afternoon:"14:30-19:30"},
   {morning:"closed",afternoon:"14:30-19:30"},
   {morning:"closed",afternoon:"15-19"},
   {morning:"10-13",afternoon:"13-19"},
   {morning:"8-13",afternoon:"14:30-19"},
   {morning:"8-13",afternoon:"14-19"},
   {morning:"8-13",afternoon:"13-20:30"},
   {morning:"9-12",afternoon:"14:30-19"},
   {morning:"0-13",afternoon:"13-0"},
]

in this way it would be easy to split the string with – and convert it to a time to be easy to show and edit in a webpage.

any suggestion on what is a good solution to achieve this?

empty need to remain empty,
open 24 hours i’m not sure it would be good to put 0-13 and 13-0 ..
i add close if part of the day is missing

thanks

Edit:
i’ve made this example of code, with few more cases, what do you think ??

const hours = [
   "",
   "Closed",
   "8:30 AM–7:30 PM", 
   "2:30–7:30 PM",
   "3–7 PM",
   "10 AM–7 PM",
   "8 AM–8:30 PM",
   "8–10:30 AM",
   "Open 24 hours",
   "9–10:30 AM, 2:30–7:30 PM",
   "9 AM–12:30 PM, 2:30–7:30 PM",
   "8 AM–1 PM, 2:30–7 PM",
   "8 AM–1 PM, 2–7 PM",
   "9 AM–12 PM, 2:30–7 PM",
]
let c = []
hours.forEach(x=>{
 c.push(convert(x))
})
console.log(c)

function convert(string) {
   switch(string) {
    case "":
      return {morning:'',afternoon:''}
      break;
    case "Closed":
      return {morning:'closed',afternoon:'closed'}
      break;
    case "Open 24 hours":
      return {morning:'0:00-13:00',afternoon:'13:00-0:00'}
      break;
  }
  if (string.includes(',')) {
    let a = string.split(',')
    let [m1,m2] = totime(a[0])
    let [a1,a2] = totime(a[1])
    return {morning:m1.format('H:mm')+'-'+m2.format('H:mm'),afternoon:a1.format('H:mm')+'-'+a2.format('H:mm')}
    
  } else {
    var endTime = moment('13:00', 'H:mm');
    let [res1,res2] = totime(stringa)
    if (res1.isBefore(endTime)){
      if (res2.isAfter(endTime)) {
        return {morning:res1.format('H:mm')+'-13:00',afternoon:'13:00-'+res2.format('H:mm')}
      } else {
        return {morning:res1.format('H:mm')+'-'+res2.format('H:mm'),afternoon:'closed'}
      }
    } else {
      return {morning:'closed',afternoon:res1.format('H:mm')+'-'+res2.format('H:mm')}
    }
  }
}

function totime(s){
  let b = s.split('–')
  if (!(b[0].includes('AM') || b[0].includes('PM'))) {
    let p = b[1].trim().split(" ")
    b[0] = b[0].trim()+" "+p[1]
  }
  return [moment(b[0].trim(), ['h:m A', 'h:m']),moment(b[1].trim(), ['h:m A', 'h:m'])]
}

3

Answers


  1. you can use javascript string manipulation and time parsing. you can create a function that takes an array for the opening hours and iterate over each time range, and inside of it you can use the if statement to handle different cases like empty strings, closed hours, and open 24 hours. and another function to convert time to 24h format. and you call your function to convert to 24h inside of your function for opening hours

    Login or Signup to reply.
  2. Be careful, it looks like you have Unicode spaces, commas, and hyphens. I converted them into their ASCII equivalents in the data array.

    To make this easier, try to parse each slot into an array of time ranges first. This makes the logic much simpler.

    const data = [
       "",
       "Closed",
       "8:30 AM-7:30 PM",
       "9 AM-12:30 PM, 2:30-7:30 PM",
       "2:30-7:30 PM",
       "3-7 PM",
       "10 AM-7 PM",
       "8 AM-1 PM, 2:30-7 PM",
       "8 AM-1 PM, 2-7 PM",
       "8 AM-8:30 PM",
       "9 AM-12 PM, 2:30-7 PM",
       "Open 24 hours",
    ];
    
    const main = () => {
      const timeRanges = parseTimeSlots(data);
      console.log(timeRanges);
    };
    
    const parseTimeSlots = (slots) =>
      slots.map(slot => {
        let morning, afternoon;
        if (slot === '') {
          morning = afternoon = '';
        } else if (slot === 'Closed') {
          morning = afternoon = 'closed'
        } else if (slot === 'Open 24 hours') {
          morning = '0-13';
          afternoon = '13-0';
        } else {
          const ranges = slot.split(/,s*/g).map(range => parseRange(range));
          if (ranges.length === 1) {
            const startRange = { start: ranges[0].start, end: { hour: 13 } };
            const endRange = { start: { hour: 13 }, end: ranges[0].end };
            
            morning = formatRange(startRange);
            afternoon = formatRange(endRange);
            
            if (ranges[0].start.hour > 13) {
              morning = 'closed';
              afternoon = formatRange(ranges[0]);
            } else {}
            if (ranges[0].end.hour < 13) {
              morning = formatRange(ranges[0]);
              afternoon = 'closed';
            }
          } else {
            morning = formatRange(ranges[0]);
            afternoon = formatRange(ranges[1]);
          }
        }
        return { morning, afternoon };
      });
      
    const formatRange = ({ start, end }) => {
      return [start, end].map(formatTime).join('-');
    };
    
    const formatTime = ({ hour, minute = 0 }) => {
      return minute ? `${hour}:${minute}` : hour;
    };
    
    const parseRange = (range) => {
      const tokens = range.split(/-/g);
      if (!tokens[0].endsWith('M')) {
        tokens[0] += ` ${tokens[1].slice(-2)}`;
      }
      return {
        start: parseTime(tokens[0]),
        end: parseTime(tokens[1])
      };
    };
    
    const parseTime = (time) => {
      const tokens = time.split(/[: ]/g);
      let hour = +tokens[0],
        minute = tokens.length > 2 ? +tokens[1] : 0,
        meridiem = tokens[tokens.length - 1];
      if (meridiem === 'PM' && hour < 12) {
        hour += 12;
      }
      return { hour, minute };
    };
    
    main();
    .as-console-wrapper { top: 0; max-height: 100% !important; }

    Reversed transformation

    You will need to parse each time range into separate hours and minutes.

    Once you have done that, you can start to compare the start o the afternoon range with the end of the morning range. This will determine if you are working with one giant range or two smaller sub-ranges.

    const data = [
       { morning: ""        , afternoon: ""            },
       { morning: "closed"  , afternoon: "closed"      },
       { morning: "8:30-13" , afternoon: "13-19:30"    },
       { morning: "9-12:30" , afternoon: "14:30-19:30" },
       { morning: "closed"  , afternoon: "14:30-19:30" },
       { morning: "closed"  , afternoon: "15-19"       },
       { morning: "10-13"   , afternoon: "13-19"       },
       { morning: "8-13"    , afternoon: "14:30-19"    },
       { morning: "8-13"    , afternoon: "14-19"       },
       { morning: "8-13"    , afternoon: "13-20:30"    },
       { morning: "9-12"    , afternoon: "14:30-19"    },
       { morning: "0-13"    , afternoon: "13-0"        },
    ];
    
    const main = () => {
      const hours = computeHours(data);
      console.log(hours);
    };
    
    const computeHours = (data) =>
      data.map(({ morning, afternoon }) => {
        if (!morning && !afternoon) return "";
        if (morning === 'closed' && afternoon === 'closed') return "Closed";
        const morningHours = morning.split('-').map(parseTime),
          afternoonHours = afternoon.split('-').map(parseTime);
        if (isAllDay(morningHours, afternoonHours)) return 'Open 24 Hours';
        if (timeMatches(morningHours[1], afternoonHours[0])) {
          return formatTimeRange(morningHours[0], afternoonHours[1]);
        } else {
          return [morningHours, afternoonHours]
            .map(range => formatTimeRange(...range))
            .filter(str => str !== null)
            .join(', ');
        }
      });
    
    const parseTime = (timeStr) => {
      if (!timeStr || timeStr === 'closed') return null;
      const tokens = timeStr.split(':').map(t => +t);
      return { hour: tokens[0], minute: tokens[1] ?? 0 };
    };
    
    const formatTime = (time, showMeridiem) => {
      if (!time) return '';
      let { hour, minute } = time, meridiem = 'AM';
      if (hour >= 12) {
        meridiem = 'PM';
      }
      if (hour > 12) {
        hour -= 12;
      }
      let result = minute ? `${hour}:${minute}` : hour;
      if (showMeridiem) {
        result += ` ${meridiem}`;
      }
      return result;
    };
    
    const formatTimeRange = (a, b) => {
      const showMeridiem = a && b && a.hour < 12 && b.hour >= 12,
        start = formatTime(a, showMeridiem),
        end = formatTime(b, true);
      if (!start && !end) return null;
      return `${start}-${end}`;
    };
    
    const timeMatches = (a, b) =>
      a && b && timeToMinutes(a) === timeToMinutes(b);
    
    const timeToMinutes = (time) => {
      const { hour, minute = 0 } = time ?? {};
      return hour * 60 + minute;
    };
    
    const isAllDay = (rangeA, rangeB) => rangeA && rangeB &&
      timeToMinutes(rangeA[0]) === 0 &&
      timeToMinutes(rangeA[1]) === timeToMinutes(rangeB[0]) &&
      timeToMinutes(rangeB[1]) === 0;
    
    main();
    .as-console-wrapper { top: 0; max-height: 100% !important; }
    Login or Signup to reply.
  3. One possible parsing approach could be based on a combination of regex result, some conditions which operate upon the regex result and a configuration for the not matching special/edge cases.

    A RegExp pattern allows for more parsing flexibility like covering too many or missing whitespaces, accidentally different separator characters like versus - or (mixed) letter casing like AM versus am.

    Such a regex pattern could utilize named groups (which are also partially optional) in order to provide meaningful names for captured data which is crucial for the parsing process. Upon the latter one would implement the conditions / clauses for creating different variants of business-hours data.

    Any input values which does not match the regex pattern is going to be sanitized and unified in order to serve as key for an object-based configuration that covers all the special cases like empty values, entirely closed or open around the clock.

    function parseBusinessHoursFromGroups(groups) {
      function toPM24(value) {
        let [ hour = 0, minutes = [] ] = value.split(':');
    
        hour = parseInt(hour, 10) + 12;
        if (hour >= 24) {
          hour = hour - 12;
        }
        return [hour].concat(minutes).join(':');
      }
      let result;
    
      let {
        fourthAPM = '', thirdAPM = fourthAPM,
        secondAPM = '', firstAPM = secondAPM,
        firstOpening, // - always defined.
        firstClosing, // - always defined.
        secondOpening = null,
        secondClosing = null,
      } = groups;
    
      // - unifiy possibly different appearing 'AM'/'PM'
      //   values each to its lowercase variant.
    
      [ fourthAPM, thirdAPM, secondAPM, firstAPM ] =
        [fourthAPM, thirdAPM, secondAPM, firstAPM]
          .map(apmValue => apmValue.toLowerCase());
    
      if (secondOpening === null) {
        if (secondAPM === firstAPM) {
    
          // - available / open just half of the day.
    
          if (secondAPM === 'am') {
            result = {
              morning: [firstOpening, firstClosing].join('–'),
              afternoon: 'closed',
            };
          } else if (secondAPM === 'pm') {
            result = {
              morning: 'closed',
              afternoon: [
                toPM24(firstOpening),
                toPM24(firstClosing),
              ].join('–'),
            };
          }
        } else {
    
          // - available / open during noon.
    
          result = {
            morning: [firstOpening, 13].join('–'),
            afternoon: [13, toPM24(firstClosing)].join('–'),
          };
        }
      } else {
    
          // - two separate opening hours during the day.
          // - unavailable / not open during noon.
    
          firstClosing = (secondAPM === 'pm')
            && toPM24(firstClosing)
            || firstClosing;
      
          result = {
            morning: [firstOpening, firstClosing].join('–'),
            afternoon: [
              toPM24(secondOpening),
              toPM24(secondClosing),
            ].join('–'),
          };
      }
      return result;
    
      // // for development logging
      // return groups;
    }
    
    function parseBusinessHoursFromString(value) {
      let result;
      value = String(value);
    
      const regXParse =
        // see ... [https://regex101.com/r/VCVz8C/1]
        /(?<firstOpening>d+(?::d+)?)s*(?<firstAPM>[AP]M)?s*[–-]s*(?<firstClosing>d+(?::d+)?)s*(?<secondAPM>[AP]M)?(?:s*,s*(?<secondOpening>d+(?::d+)?)s*(?<thirdAPM>[AP]M)?s*[–-]s*(?<secondClosing>d+(?::d+)?)s*(?<fourthAPM>[AP]M))?/gi;
    
      const groups = regXParse.exec(value)?.groups ?? null;
    
      return (groups !== null)
        && parseBusinessHoursFromGroups(groups)
        || ({
          // - object based configuration.
    
          '': { morning: '', afternoon: '' },
          'closed': { morning: 'closed', afternoon: 'closed' },
          'open 24 hours': { morning: '0-13', afternoon: '13-0' },
    
        })[value.trim().replace(/s+/g, ' ').toLowerCase()];
    };
    console.log([
    
      "",
      "Closed",
      "8:30 AM–7:30 PM",
      "9 AM–12:30 PM, 2:30–7:30 PM",
      "2:30–7:30 PM",
      "3–7 PM",
      "10 AM–7 PM",
      "8 AM–1 PM, 2:30–7 PM",
      "8 AM–1 PM, 2–7 PM",
      "8 AM–8:30 PM",
      "9 AM–12 PM, 2:30–7 PM",
      "Open 24 hours",
    
      // additional test cases provided by the OP
    
      "8–10:30 AM",
      "9–10:30 AM, 2:30–7:30 PM",
    
    ].map(parseBusinessHoursFromString));
    .as-console-wrapper { min-height: 100%!important; top: 0; }
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search