regex 将开放时间字符串转换为js中的时间

luaexgnf  于 2023-06-25  发布在  其他
关注(0)|答案(3)|浏览(93)

我有一个开放时间的列表,或多或少都是这种格式,一个值代表一整天:

[
   "",
   "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",
]

当然,时间值可以改变,但我需要在上午和下午的开放时间转换这些值,所以我需要将这些字符串转换为时间元素,然后使用13:00(下午1点)作为上午的结束,为每行生成如下内容:

[
   {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"},
]

以这种方式,可以容易地分割字符串,并将其转换为易于在网页中显示和编辑的时间。
有什么建议是一个很好的解决方案,以实现这一点?
空需要保持空,开放24小时,我不知道这将是好的,把0 - 13和13 - 0。如果一天中的一部分丢失,我会添加关闭
谢谢
编辑:我已经做了这个例子的代码,与几个更多的情况下,你觉得呢??

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'])]
}
bmp9r5qi

bmp9r5qi1#

你可以使用JavaScript字符串操作和时间解析。你可以创建一个函数,它接受一个开放时间的数组,并在每个时间范围内迭代,在里面你可以使用if语句来处理不同的情况,如空字符串,关闭时间和开放24小时。另一个功能是将时间转换为24h格式。调用函数将函数内部的开放时间转换为24h

bfnvny8b

bfnvny8b2#

小心,看起来你有Unicode空格、逗号和连字符。我将它们转换为data数组中的ASCII等价物。
为了简化这一点,请先尝试将每个时隙解析成一个时间范围数组。这使得逻辑更加简单。

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; }

反向转换

您需要将每个时间范围解析为单独的小时和分钟。
一旦你这样做了,你就可以开始比较下午范围的开始和上午范围的结束。这将决定您是使用一个巨大的范围还是两个较小的子范围。

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; }
e3bfsja2

e3bfsja23#

一种可能的解析方法可以基于正则表达式结果、对正则表达式结果进行操作的一些条件以及用于不匹配的特殊/边缘情况的配置的组合。
RegExp模式允许更多的解析灵活性,例如覆盖太多或丢失空格,意外不同的分隔符,如-或(混合)字母大小写,如AMam
Such a regex pattern可以利用命名组(也是部分可选的),以便为所捕获的数据提供有意义的名称,这对于解析过程是至关重要的。在后者上,将实现用于创建营业时间数据的不同变体的条件/子句。
任何与正则表达式模式不匹配的输入值都将被清理和统一,以便作为基于对象的配置的关键,该配置涵盖所有特殊情况,如空值,完全关闭或全天候开放。

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; }

相关问题