private static void SetFormat(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            TimeSpanPicker timeSpanPicker = (TimeSpanPicker)obj;

            timeSpanPicker.parts.Clear();
            IndexedDateTimePart currentPart = null;
            char previousFormatCharacter = '\0';
            for (int formatIndex = 0, selectionIndex = 0; formatIndex < timeSpanPicker.Format.Length; ++formatIndex, ++selectionIndex)
            {
                char formatCharacter = timeSpanPicker.Format[formatIndex];
                switch (formatCharacter)
                {
                    case 'd': // days
                    case 'f': // milliseconds
                    case 'F':
                    case 'h': // hours
                    case 'm': // minutes
                    case 's': // seconds
                        break;
                    case '\\':
                        // backslashes aren't displayed and hence aren't part of selection
                        --selectionIndex;
                        continue;
                    case '.':
                    case ':':
                    case ' ':
                        // skip delimiters and spaces
                        continue;
                    default:
                        throw new NotSupportedException(String.Format("Unsupported format character '{0}'.", formatCharacter));
                }

                if (formatCharacter != previousFormatCharacter)
                {
                    // encountered new part
                    currentPart = new IndexedDateTimePart(formatCharacter, formatIndex, selectionIndex);
                    timeSpanPicker.parts.Add(currentPart);
                    previousFormatCharacter = formatCharacter;
                }
                else
                {
                    // still in same part
                    ++currentPart.FormatLength;
                    ++currentPart.SelectionLength;
                }
            }

            // the first part may have a minus sign and therefore has variable selection indicies
            if (timeSpanPicker.parts.Count > 0)
            {
                timeSpanPicker.parts[0].IsSelectionStartVariable = true;
            }

            // ensure part index is valid
            if (timeSpanPicker.parts.Count < 1)
            {
                timeSpanPicker.currentPartIndex = -1;
            }
            else if (timeSpanPicker.currentPartIndex < 0)
            {
                timeSpanPicker.currentPartIndex = 0;
            }
            else if (timeSpanPicker.currentPartIndex > timeSpanPicker.parts.Count - 1)
            {
                timeSpanPicker.currentPartIndex = timeSpanPicker.parts.Count - 1;
            }

            // ensure displayed value uses current format
            timeSpanPicker.TimeSpanDisplay.Text = timeSpanPicker.Value.ToString(timeSpanPicker.Format);
        }
        private static void SetFormat(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            DateTimeOffsetPicker dateTimeOffsetPicker = (DateTimeOffsetPicker)obj;

            dateTimeOffsetPicker.monthPartIndex = Int32.MaxValue;
            dateTimeOffsetPicker.parts.Clear();

            IndexedDateTimePart currentPart = null;
            char previousFormatCharacter = '\0';
            for (int formatIndex = 0, selectionIndex = 0; formatIndex < dateTimeOffsetPicker.Format.Length; ++formatIndex, ++selectionIndex)
            {
                char formatCharacter = dateTimeOffsetPicker.Format[formatIndex];
                switch (formatCharacter)
                {
                    case 'd': // day
                    case 'f': // millisecond
                    case 'F':
                    case 'h': // hour
                    case 'H':
                    case Constant.Time.DateTimeOffsetPart: // offset
                    case 'm': // minute
                    case 'M': // month
                    case 's': // second
                    case 'y': // year
                    case 'z': // offset
                        break;
                    case '\\':
                        // backslashes aren't displayed and hence aren't part of selection
                        --selectionIndex;
                        continue;
                    case '.':
                    case ':':
                    case '/':
                    case '-':
                    case ' ':
                    case 'T':
                    case 'Z':
                        // skip delimiters and UTC indicator
                        continue;
                    default:
                        throw new NotSupportedException(String.Format("Unsupported format character '{0}'.", formatCharacter));
                }

                if (formatCharacter != previousFormatCharacter)
                {
                    if ((currentPart != null) && (currentPart.Format == 'M') && (currentPart.FormatLength == 3))
                    {
                        // part which just ended is an MMM month part and may render as two to eight or more characters depending on the current culture
                        // If needed this check can be done after the loop completes but there's no scenario for putting the month at the end of the date or
                        // not following it with some other part.  Lengths of zero are excluded as most calendars have 12 months.
                        List<int> abbreviatedMonthNameLengths = CultureInfo.CurrentCulture.DateTimeFormat.AbbreviatedMonthNames.Select(name => name.Length).Distinct().Where(length => length > 0).ToList();
                        int minimumSelectionLength = abbreviatedMonthNameLengths.Min();
                        currentPart.IsSelectionLengthVariable = abbreviatedMonthNameLengths.Count > 1;
                        currentPart.SelectionLength = minimumSelectionLength;

                        dateTimeOffsetPicker.monthPartIndex = dateTimeOffsetPicker.parts.Count - 1;
                        selectionIndex += minimumSelectionLength - 3;
                    }

                    // encountered new part
                    currentPart = new IndexedDateTimePart(formatCharacter, formatIndex, selectionIndex);
                    dateTimeOffsetPicker.parts.Add(currentPart);
                    previousFormatCharacter = formatCharacter;

                    if (formatCharacter == Constant.Time.DateTimeOffsetPart)
                    {
                        // current part is hours part, which may have a minus sign and therefore has a variable start
                        currentPart.IsSelectionStartVariable = true;
                        currentPart.SelectionLength = 2;

                        // add minutes part
                        IndexedDateTimePart minutesPart = new IndexedDateTimePart(formatCharacter, formatIndex, currentPart.SelectionStart + currentPart.SelectionLength + 1)
                        {
                            SelectionLength = 2
                        };
                        dateTimeOffsetPicker.parts.Add(minutesPart);
                    }
                }
                else
                {
                    // still in same part
                    ++currentPart.FormatLength;
                    ++currentPart.SelectionLength;
                }
            }

            // ensure part index is valid
            if (dateTimeOffsetPicker.parts.Count < 1)
            {
                dateTimeOffsetPicker.currentPartIndex = -1;
            }
            else if (dateTimeOffsetPicker.currentPartIndex < 0)
            {
                dateTimeOffsetPicker.currentPartIndex = 0;
            }
            else if (dateTimeOffsetPicker.currentPartIndex > dateTimeOffsetPicker.parts.Count - 1)
            {
                dateTimeOffsetPicker.currentPartIndex = dateTimeOffsetPicker.parts.Count - 1;
            }

            // ensure displayed value uses current format
            dateTimeOffsetPicker.DateTimeDisplay.Text = dateTimeOffsetPicker.Value.ToString(dateTimeOffsetPicker.Format);
        }