/// <summary>
        /// create instance if this has nonnull RepeatInfo, or null
        /// </summary>
        internal static ParsedRepeatInfo Build(string packedValue)
        {
            //not rpeating
            if (string.IsNullOrEmpty(packedValue))
            {
                return(null);
            }

            //classify the segments
            var    parsed     = new ParsedRepeatInfo();
            var    segments   = packedValue.Split('|', StringSplitOptions.RemoveEmptyEntries);
            string maxEndTime = DateUtil.ToYMDHM(DateTime.Today.AddYears(1));

            parsed.EndTime    = maxEndTime;
            parsed.AutoExtend = false;
            foreach (string segment in segments)
            {
                if (segment.Length < 1)
                {
                    continue;
                }
                char meaning = segment[0];
                if (meaning == 'e')
                {
                    if (segment.Length == 14)
                    {
                        parsed.EndTime = segment[2..];
        /// <summary>
        /// With delayed execution, find all cases where a task should be repeated
        /// </summary>
        /// <param name="box">Only inspects box.BoxTime and Repeats</param>
        /// <param name="includeCurrent">if true, includes the time the box is currently scheduled for</param>
        /// <param name="allowAbortOnLowClutter">if true, will skip projections after the currently scheduled time, if low clutter</param>
        public IEnumerable <AgendaEntry> Project(CachedBox box, bool includeCurrent, bool allowAbortOnLowClutter)
        {
            //include the specific time it is now scheduled for whether repeating or not
            if (includeCurrent)
            {
                (DateTime? remindAt1, DateTime? remindAt2) = CalcPendingReminder(box);
                yield return(new AgendaEntry
                {
                    Box = box,
                    Time = box.BoxTime,
                    PendingPrepReminder = remindAt1,
                    PendingReminder = remindAt2
                });
            }

            bool doProjections = box.Repeats != null;

            if (allowAbortOnLowClutter && box.Visibility == Constants.VISIBILITY_LOWCLUTTER)
            {
                doProjections = false;
            }
            if (doProjections)
            {
                DateTime?max = DateUtil.ToDateTime(box.Repeats.EndTime);
                if (max == null)
                {
                    max = DateTime.Today;
                }

                //convert add and delete exceptions to datetime strings and start with all the added ones
                var deleteExceptions = box.Repeats.Entries.Where(e => e.Kind == ParsedRepeatInfo.RepeatKind.DeleteSpecific)
                                       .Select(d => d.Date + d.Time);
                var addExceptions = box.Repeats.Entries.Where(e => e.Kind == ParsedRepeatInfo.RepeatKind.AddSpecific)
                                    .Select(d => d.Date + d.Time);
                var allTimes = new HashSet <string>();
                foreach (string t in addExceptions)
                {
                    allTimes.Add(t);
                }

                //go through all patterns and add times for those unless deleted
                foreach (var pat in box.Repeats.Entries)
                {
                    //handle add and delete
                    if (pat.Kind == ParsedRepeatInfo.RepeatKind.DeleteSpecific)
                    {
                        continue;
                    }
                    if (pat.Kind == ParsedRepeatInfo.RepeatKind.AddSpecific)
                    {
                        allTimes.Add(pat.Date + pat.Time);
                        continue;
                    }

                    //handle all other pattern kinds
                    (int hr, int mi) = DateUtil.ToHourMinute(pat.Time, 9, 0);
                    DateTime?running = DateUtil.ToDateTime(box.BoxTime);
                    if (running == null)
                    {
                        continue;
                    }
                    for (int iter = 0; iter < 500; ++iter) //infinite loop control
                    {
                        running = NextTime(running.Value, max.Value, pat, hr, mi);
                        if (running == null)
                        {
                            break;
                        }
                        string t2 = DateUtil.ToYMDHM(running.Value);
                        if (deleteExceptions.Contains(t2))
                        {
                            continue;
                        }
                        allTimes.Add(t2);
                    }
                }

                //provide return values
                foreach (string t in allTimes)
                {
                    yield return(new AgendaEntry
                    {
                        Box = box,
                        Time = t
                    });
                }
            }
        }