예제 #1
0
            public ParseResult <Offset> ParsePartial(ValueCursor cursor)
            {
                int startIndex = cursor.Index;
                // TODO: Do better than this. It's horrible, and may well be invalid
                // for some cultures. Or just remove the NumberPattern from 2.0...
                int longestPossible = Math.Min(maxLength, cursor.Length - cursor.Index);

                for (int length = longestPossible; length >= 0; length--)
                {
                    string candidate = cursor.Value.Substring(cursor.Index, length);
                    int    milliseconds;
                    if (Int32.TryParse(candidate, NumberStyles.Integer | NumberStyles.AllowThousands,
                                       formatInfo.NumberFormat, out milliseconds))
                    {
                        if (milliseconds < -NodaConstants.MillisecondsPerStandardDay ||
                            NodaConstants.MillisecondsPerStandardDay < milliseconds)
                        {
                            cursor.Move(startIndex);
                            return(ParseResult <Offset> .ValueOutOfRange(cursor, milliseconds));
                        }
                        cursor.Move(cursor.Index + length);
                        return(ParseResult <Offset> .ForValue(Offset.FromMilliseconds(milliseconds)));
                    }
                }
                cursor.Move(startIndex);
                return(ParseResult <Offset> .CannotParseValue(cursor, "n"));
            }
예제 #2
0
 public ParseResult <TTarget> Convert <TTarget>([NotNull] Func <T, TTarget> projection)
 {
     Preconditions.CheckNotNull(projection, nameof(projection));
     return(Success
         ? ParseResult <TTarget> .ForValue(projection(Value))
         : new ParseResult <TTarget>(exceptionProvider, ContinueAfterErrorWithMultipleFormats));
 }
            internal override ParseResult <LocalDate> CalculateValue(PatternFields usedFields)
            {
                // This will set Year if necessary
                ParseResult <LocalDate> failure = DetermineYear(usedFields);

                if (failure != null)
                {
                    return(failure);
                }
                // This will set MonthOfYearNumeric if necessary
                failure = DetermineMonth(usedFields);
                if (failure != null)
                {
                    return(failure);
                }

                int day = IsFieldUsed(usedFields, PatternFields.DayOfMonth) ? DayOfMonth : templateValue.Day;

                if (day > Calendar.GetDaysInMonth(Year, MonthOfYearNumeric))
                {
                    return(ParseResult <LocalDate> .DayOfMonthOutOfRange(day, MonthOfYearNumeric, Year));
                }

                LocalDate value = new LocalDate(Year, MonthOfYearNumeric, day, Calendar);

                if (IsFieldUsed(usedFields, PatternFields.DayOfWeek) && DayOfWeek != value.DayOfWeek)
                {
                    return(ParseResult <LocalDate> .InconsistentDayOfWeekTextValue);
                }

                return(ParseResult <LocalDate> .ForValue(value));
            }
예제 #4
0
 public ParseResult <Offset> ParsePartial(ValueCursor cursor)
 {
     if (cursor.Current == 'Z')
     {
         cursor.MoveNext();
         return(ParseResult <Offset> .ForValue(Offset.Zero));
     }
     return(fullPattern.ParsePartial(cursor));
 }
            internal override ParseResult <ZonedDateTime> CalculateValue(PatternFields usedFields, string text)
            {
                var localResult = LocalDateTimePatternParser.LocalDateTimeParseBucket.CombineBuckets(usedFields, Date, Time, text);

                if (!localResult.Success)
                {
                    return(localResult.ConvertError <ZonedDateTime>());
                }

                var localDateTime = localResult.Value;

                // No offset - so just use the resolver
                if ((usedFields & PatternFields.EmbeddedOffset) == 0)
                {
                    try
                    {
                        return(ParseResult <ZonedDateTime> .ForValue(Zone.ResolveLocal(localDateTime, resolver)));
                    }
                    catch (SkippedTimeException)
                    {
                        return(ParseResult <ZonedDateTime> .SkippedLocalTime(text));
                    }
                    catch (AmbiguousTimeException)
                    {
                        return(ParseResult <ZonedDateTime> .AmbiguousLocalTime(text));
                    }
                }

                // We were given an offset, so we can resolve and validate using that
                var           mapping = Zone.MapLocal(localDateTime);
                ZonedDateTime result;

                switch (mapping.Count)
                {
                // If the local time was skipped, the offset has to be invalid.
                case 0:
                    return(ParseResult <ZonedDateTime> .InvalidOffset(text));

                case 1:
                    result = mapping.First();     // We'll validate in a minute
                    break;

                case 2:
                    result = mapping.First().Offset == Offset?mapping.First() : mapping.Last();

                    break;

                default:
                    throw new InvalidOperationException("Mapping has count outside range 0-2; should not happen.");
                }
                if (result.Offset != Offset)
                {
                    return(ParseResult <ZonedDateTime> .InvalidOffset(text));
                }
                return(ParseResult <ZonedDateTime> .ForValue(result));
            }
            protected override ParseResult <Instant> ParseImpl(string value)
            {
                long number;

                if (Int64.TryParse(value, ParsingNumberStyles, FormatInfo.NumberFormat, out number))
                {
                    return(ParseResult <Instant> .ForValue(new Instant(number)));
                }
                return(ParseResult <Instant> .CannotParseValue(value, patternText));
            }
예제 #7
0
            /// <summary>
            /// Calculates the value from the parsed pieces.
            /// </summary>
            internal override ParseResult <Offset> CalculateValue(PatternFields usedFields, string text)
            {
                int seconds = Hours * NodaConstants.SecondsPerHour +
                              Minutes * NodaConstants.SecondsPerMinute +
                              Seconds;

                if (IsNegative)
                {
                    seconds = -seconds;
                }
                return(ParseResult <Offset> .ForValue(Offset.FromSeconds(seconds)));
            }
 public ParseResult <Instant> Parse(string text)
 {
     if (text == minLabel)
     {
         return(ParseResult <Instant> .ForValue(Instant.MinValue));
     }
     if (text == maxLabel)
     {
         return(ParseResult <Instant> .ForValue(Instant.MaxValue));
     }
     return(pattern.Parse(text).Convert(local => new Instant(local.LocalInstant.Ticks)));
 }
예제 #9
0
            internal override ParseResult <OffsetDate> CalculateValue(PatternFields usedFields, string text)
            {
                ParseResult <LocalDate> dateResult = Date.CalculateValue(usedFields & PatternFields.AllDateFields, text);

                if (!dateResult.Success)
                {
                    return(dateResult.ConvertError <OffsetDate>());
                }
                LocalDate date = dateResult.Value;

                return(ParseResult <OffsetDate> .ForValue(date.WithOffset(Offset)));
            }
예제 #10
0
            /// <summary>
            /// Calculates the value from the parsed pieces.
            /// </summary>
            internal override ParseResult <Offset> CalculateValue(PatternFields usedFields)
            {
                int milliseconds = Hours * NodaConstants.MillisecondsPerHour +
                                   Minutes * NodaConstants.MillisecondsPerMinute +
                                   Seconds * NodaConstants.MillisecondsPerSecond +
                                   Milliseconds;

                if (IsNegative)
                {
                    milliseconds = -milliseconds;
                }
                return(ParseResult <Offset> .ForValue(Offset.FromMilliseconds(milliseconds)));
            }
예제 #11
0
 /// <summary>
 /// Calculates the value from the parsed pieces.
 /// </summary>
 internal override ParseResult <Duration> CalculateValue(PatternFields usedFields, string text)
 {
     if (IsNegative)
     {
         currentNanos = -currentNanos;
     }
     if (currentNanos < Duration.MinNanoseconds || currentNanos > Duration.MaxNanoseconds)
     {
         return(ParseResult <Duration> .ForInvalidValuePostParse(text, TextErrorMessages.OverallValueOutOfRange,
                                                                 typeof(Duration)));
     }
     return(ParseResult <Duration> .ForValue(Duration.FromNanoseconds(currentNanos)));
 }
            internal override ParseResult <OffsetDateTime> CalculateValue(PatternFields usedFields, string text)
            {
                var localResult = LocalDateTimePatternParser.LocalDateTimeParseBucket.CombineBuckets(usedFields, Date, Time, text);

                if (!localResult.Success)
                {
                    return(localResult.ConvertError <OffsetDateTime>());
                }

                var localDateTime = localResult.Value;

                return(ParseResult <OffsetDateTime> .ForValue(localDateTime.WithOffset(Offset)));
            }
 /// <summary>
 /// Calculates the value from the parsed pieces.
 /// </summary>
 internal override ParseResult <Duration> CalculateValue(PatternFields usedFields, string text)
 {
     if (IsNegative)
     {
         currentNanos = -currentNanos;
     }
     if (currentNanos < MinNanos || currentNanos > MaxNanos)
     {
         // TODO: Work out whether this is really the best message. (Created a new one...)
         return(ParseResult <Duration> .ForInvalidValuePostParse(text, Messages.Parse_OverallValueOutOfRange,
                                                                 typeof(Duration)));
     }
     return(ParseResult <Duration> .ForValue(Duration.FromNanoseconds(currentNanos)));
 }
예제 #14
0
            public ParseResult <Offset> Parse(string text)
            {
                int milliseconds;

                if (Int32.TryParse(text, NumberStyles.Integer | NumberStyles.AllowThousands,
                                   formatInfo.NumberFormat, out milliseconds))
                {
                    if (milliseconds < -NodaConstants.MillisecondsPerStandardDay ||
                        NodaConstants.MillisecondsPerStandardDay < milliseconds)
                    {
                        return(ParseResult <Offset> .ValueOutOfRange(new ValueCursor(text), milliseconds));
                    }
                    return(ParseResult <Offset> .ForValue(Offset.FromMilliseconds(milliseconds)));
                }
                return(ParseResult <Offset> .CannotParseValue(new ValueCursor(text), "n"));
            }
예제 #15
0
            /// <summary>
            /// Calculates the value from the parsed pieces.
            /// </summary>
            internal override ParseResult <LocalTime> CalculateValue(PatternFields usedFields, string text)
            {
                if (AmPm == 2)
                {
                    AmPm = templateValue.Hour / 12;
                }
                int hour;
                ParseResult <LocalTime> failure = DetermineHour(usedFields, text, out hour);

                if (failure != null)
                {
                    return(failure);
                }
                int minutes  = IsFieldUsed(usedFields, PatternFields.Minutes) ? Minutes : templateValue.Minute;
                int seconds  = IsFieldUsed(usedFields, PatternFields.Seconds) ? Seconds : templateValue.Second;
                int fraction = IsFieldUsed(usedFields, PatternFields.FractionalSeconds) ? FractionalSeconds : templateValue.TickOfSecond;

                return(ParseResult <LocalTime> .ForValue(LocalTime.FromHourMinuteSecondTick(hour, minutes, seconds, fraction)));
            }
예제 #16
0
            internal override ParseResult <AnnualDate> CalculateValue(PatternFields usedFields, string text)
            {
                // This will set MonthOfYearNumeric if necessary
                var failure = DetermineMonth(usedFields, text);

                if (failure != null)
                {
                    return(failure);
                }

                int day = usedFields.HasAny(PatternFields.DayOfMonth) ? DayOfMonth : TemplateValue.Day;

                // Validate for the year 2000, just like the AnnualDate constructor does.
                if (day > CalendarSystem.Iso.GetDaysInMonth(2000, MonthOfYearNumeric))
                {
                    return(ParseResult <AnnualDate> .DayOfMonthOutOfRangeNoYear(text, day, MonthOfYearNumeric));
                }

                return(ParseResult <AnnualDate> .ForValue(new AnnualDate(MonthOfYearNumeric, day)));
            }
            /// <summary>
            /// Combines the values in a date bucket with the values in a time bucket.
            /// </summary>
            /// <remarks>
            /// This would normally be the <see cref="CalculateValue"/> method, but we want
            /// to be able to use the same logic when parsing an <see cref="OffsetDateTime"/>
            /// and <see cref="ZonedDateTime"/>.
            /// </remarks>
            internal static ParseResult <LocalDateTime> CombineBuckets(
                PatternFields usedFields,
                LocalDatePatternParser.LocalDateParseBucket dateBucket,
                LocalTimePatternParser.LocalTimeParseBucket timeBucket,
                string text)
            {
                // Handle special case of hour = 24
                bool hour24 = false;

                if (timeBucket.Hours24 == 24)
                {
                    timeBucket.Hours24 = 0;
                    hour24             = true;
                }

                ParseResult <LocalDate> dateResult = dateBucket.CalculateValue(usedFields & PatternFields.AllDateFields, text);

                if (!dateResult.Success)
                {
                    return(dateResult.ConvertError <LocalDateTime>());
                }
                ParseResult <LocalTime> timeResult = timeBucket.CalculateValue(usedFields & PatternFields.AllTimeFields, text);

                if (!timeResult.Success)
                {
                    return(timeResult.ConvertError <LocalDateTime>());
                }

                LocalDate date = dateResult.Value;
                LocalTime time = timeResult.Value;

                if (hour24)
                {
                    if (time != LocalTime.Midnight)
                    {
                        return(ParseResult <LocalDateTime> .InvalidHour24(text));
                    }
                    date = date.PlusDays(1);
                }
                return(ParseResult <LocalDateTime> .ForValue(date + time));
            }
예제 #18
0
            /// <summary>
            /// Calculates the value from the parsed pieces.
            /// </summary>
            internal override ParseResult <LocalTime> CalculateValue(PatternFields usedFields, string text)
            {
                if (usedFields.HasAny(PatternFields.EmbeddedTime))
                {
                    return(ParseResult <LocalTime> .ForValue(LocalTime.FromHourMinuteSecondNanosecond(Hours24, Minutes, Seconds, FractionalSeconds)));
                }
                if (AmPm == 2)
                {
                    AmPm = TemplateValue.Hour / 12;
                }
                ParseResult <LocalTime> failure = DetermineHour(usedFields, text, out int hour);

                if (failure != null)
                {
                    return(failure);
                }
                int minutes  = usedFields.HasAny(PatternFields.Minutes) ? Minutes : TemplateValue.Minute;
                int seconds  = usedFields.HasAny(PatternFields.Seconds) ? Seconds : TemplateValue.Second;
                int fraction = usedFields.HasAny(PatternFields.FractionalSeconds) ? FractionalSeconds : TemplateValue.NanosecondOfSecond;

                return(ParseResult <LocalTime> .ForValue(LocalTime.FromHourMinuteSecondNanosecond(hour, minutes, seconds, fraction)));
            }
예제 #19
0
            internal override ParseResult <LocalDate> CalculateValue(PatternFields usedFields, string text)
            {
                if (usedFields.HasAny(PatternFields.EmbeddedDate))
                {
                    return(ParseResult <LocalDate> .ForValue(new LocalDate(Year, MonthOfYearNumeric, DayOfMonth, Calendar)));
                }
                // This will set Year if necessary
                ParseResult <LocalDate> failure = DetermineYear(usedFields, text);

                if (failure != null)
                {
                    return(failure);
                }
                // This will set MonthOfYearNumeric if necessary
                failure = DetermineMonth(usedFields, text);
                if (failure != null)
                {
                    return(failure);
                }

                int day = usedFields.HasAny(PatternFields.DayOfMonth) ? DayOfMonth : TemplateValue.Day;

                if (day > Calendar.GetDaysInMonth(Year, MonthOfYearNumeric))
                {
                    return(ParseResult <LocalDate> .DayOfMonthOutOfRange(text, day, MonthOfYearNumeric, Year));
                }

                LocalDate value = new LocalDate(Year, MonthOfYearNumeric, day, Calendar);

                if (usedFields.HasAny(PatternFields.DayOfWeek) && DayOfWeek != value.DayOfWeek)
                {
                    return(ParseResult <LocalDate> .InconsistentDayOfWeekTextValue(text));
                }

                // FIXME: If we got an era, check that the resulting date really lies within that era.
                return(ParseResult <LocalDate> .ForValue(value));
            }
예제 #20
0
 /// <summary>
 /// Calculates the value from the parsed pieces.
 /// </summary>
 internal override ParseResult <Duration> CalculateValue(PatternFields usedFields, string text)
 {
     return(ParseResult <Duration> .ForValue(Duration.FromTicks(IsNegative ? NegativeTicks : -NegativeTicks)));
 }
예제 #21
0
 /// <summary>
 /// Converts this result to a new target type, either by executing the given projection
 /// for a success result, or propagating the exception provider for failure.
 /// </summary>
 internal ParseResult <TTarget> Convert <TTarget>(NodaFunc <T, TTarget> projection)
 {
     return(Success ? ParseResult <TTarget> .ForValue(projection(Value))
         : new ParseResult <TTarget>(exceptionProvider, continueWithMultiple));
 }
예제 #22
0
 /// <summary>
 /// Converts this result to a new target type, either by executing the given projection
 /// for a success result, or propagating the exception provider for failure.
 /// </summary>
 internal ParseResult <TTarget> Convert <TTarget>(Func <T, TTarget> projection) =>
 Success ? ParseResult <TTarget> .ForValue(projection(Value))
             : new ParseResult <TTarget>(exceptionProvider, ContinueAfterErrorWithMultipleFormats);
예제 #23
0
            // TODO(misc): Tidy this up a *lot*.
            public ParseResult <Period> Parse(string text)
            {
                if (text is null)
                {
                    return(ParseResult <Period> .ArgumentNull("text"));
                }
                if (text.Length == 0)
                {
                    return(ParseResult <Period> .ValueStringEmpty);
                }

                ValueCursor valueCursor = new ValueCursor(text);

                valueCursor.MoveNext();
                if (valueCursor.Current != 'P')
                {
                    return(ParseResult <Period> .MismatchedCharacter(valueCursor, 'P'));
                }
                bool          inDate     = true;
                PeriodBuilder builder    = new PeriodBuilder();
                PeriodUnits   unitsSoFar = 0;

                while (valueCursor.MoveNext())
                {
                    if (inDate && valueCursor.Current == 'T')
                    {
                        inDate = false;
                        continue;
                    }
                    bool negative = valueCursor.Current == '-';
                    var  failure  = valueCursor.ParseInt64 <Period>(out long unitValue);
                    if (failure != null)
                    {
                        return(failure);
                    }
                    if (valueCursor.Length == valueCursor.Index)
                    {
                        return(ParseResult <Period> .EndOfString(valueCursor));
                    }
                    // Various failure cases:
                    // - Repeated unit (e.g. P1M2M)
                    // - Time unit is in date part (e.g. P5M)
                    // - Date unit is in time part (e.g. PT1D)
                    // - Unit is in incorrect order (e.g. P5D1Y)
                    // - Unit is invalid (e.g. P5J)
                    // - Unit is missing (e.g. P5)
                    PeriodUnits unit;
                    switch (valueCursor.Current)
                    {
                    case 'Y': unit = PeriodUnits.Years; break;

                    case 'M': unit = inDate ? PeriodUnits.Months : PeriodUnits.Minutes; break;

                    case 'W': unit = PeriodUnits.Weeks; break;

                    case 'D': unit = PeriodUnits.Days; break;

                    case 'H': unit = PeriodUnits.Hours; break;

                    case 'S': unit = PeriodUnits.Seconds; break;

                    case ',':
                    case '.': unit = PeriodUnits.Nanoseconds; break;     // Special handling below

                    default: return(InvalidUnit(valueCursor, valueCursor.Current));
                    }
                    if ((unit & unitsSoFar) != 0)
                    {
                        return(RepeatedUnit(valueCursor, valueCursor.Current));
                    }

                    // This handles putting months before years, for example. Less significant units
                    // have higher integer representations.
                    if (unit < unitsSoFar)
                    {
                        return(MisplacedUnit(valueCursor, valueCursor.Current));
                    }

                    // The result of checking "there aren't any time units in this unit" should be
                    // equal to "we're still in the date part".
                    if ((unit & PeriodUnits.AllTimeUnits) == 0 != inDate)
                    {
                        return(MisplacedUnit(valueCursor, valueCursor.Current));
                    }

                    // Seen a . or , which need special handling.
                    if (unit == PeriodUnits.Nanoseconds)
                    {
                        // Check for already having seen seconds, e.g. PT5S0.5
                        if ((unitsSoFar & PeriodUnits.Seconds) != 0)
                        {
                            return(MisplacedUnit(valueCursor, valueCursor.Current));
                        }
                        builder.Seconds = unitValue;

                        if (!valueCursor.MoveNext())
                        {
                            return(ParseResult <Period> .MissingNumber(valueCursor));
                        }
                        // Can cope with at most 999999999 nanoseconds
                        if (!valueCursor.ParseFraction(9, 9, out int totalNanoseconds, 1))
                        {
                            return(ParseResult <Period> .MissingNumber(valueCursor));
                        }
                        // Use whether or not the seconds value was negative (even if 0)
                        // as the indication of whether this value is negative.
                        if (negative)
                        {
                            totalNanoseconds = -totalNanoseconds;
                        }
                        builder.Milliseconds = (totalNanoseconds / NanosecondsPerMillisecond) % MillisecondsPerSecond;
                        builder.Ticks        = (totalNanoseconds / NanosecondsPerTick) % TicksPerMillisecond;
                        builder.Nanoseconds  = totalNanoseconds % NanosecondsPerTick;

                        if (valueCursor.Current != 'S')
                        {
                            return(ParseResult <Period> .MismatchedCharacter(valueCursor, 'S'));
                        }
                        if (valueCursor.MoveNext())
                        {
                            return(ParseResult <Period> .ExpectedEndOfString(valueCursor));
                        }
                        return(ParseResult <Period> .ForValue(builder.Build()));
                    }

                    builder[unit] = unitValue;
                    unitsSoFar   |= unit;
                }
                if (unitsSoFar == 0)
                {
                    return(ParseResult <Period> .ForInvalidValue(valueCursor, TextErrorMessages.EmptyPeriod));
                }
                return(ParseResult <Period> .ForValue(builder.Build()));
            }
예제 #24
0
            public ParseResult <Period> Parse(string text)
            {
                if (text is null)
                {
                    return(ParseResult <Period> .ArgumentNull("text"));
                }
                if (text.Length == 0)
                {
                    return(ParseResult <Period> .ValueStringEmpty);
                }

                ValueCursor valueCursor = new ValueCursor(text);

                valueCursor.MoveNext();
                if (valueCursor.Current != 'P')
                {
                    return(ParseResult <Period> .MismatchedCharacter(valueCursor, 'P'));
                }
                bool          inDate     = true;
                PeriodBuilder builder    = new PeriodBuilder();
                PeriodUnits   unitsSoFar = 0;

                while (valueCursor.MoveNext())
                {
                    if (inDate && valueCursor.Current == 'T')
                    {
                        inDate = false;
                        continue;
                    }
                    var failure = valueCursor.ParseInt64 <Period>(out long unitValue);
                    if (failure != null)
                    {
                        return(failure);
                    }
                    if (valueCursor.Length == valueCursor.Index)
                    {
                        return(ParseResult <Period> .EndOfString(valueCursor));
                    }
                    // Various failure cases:
                    // - Repeated unit (e.g. P1M2M)
                    // - Time unit is in date part (e.g. P5M)
                    // - Date unit is in time part (e.g. PT1D)
                    // - Unit is in incorrect order (e.g. P5D1Y)
                    // - Unit is invalid (e.g. P5J)
                    // - Unit is missing (e.g. P5)
                    PeriodUnits unit;
                    switch (valueCursor.Current)
                    {
                    case 'Y': unit = PeriodUnits.Years; break;

                    case 'M': unit = inDate ? PeriodUnits.Months : PeriodUnits.Minutes; break;

                    case 'W': unit = PeriodUnits.Weeks; break;

                    case 'D': unit = PeriodUnits.Days; break;

                    case 'H': unit = PeriodUnits.Hours; break;

                    case 'S': unit = PeriodUnits.Seconds; break;

                    case 's': unit = PeriodUnits.Milliseconds; break;

                    case 't': unit = PeriodUnits.Ticks; break;

                    case 'n': unit = PeriodUnits.Nanoseconds; break;

                    default: return(InvalidUnit(valueCursor, valueCursor.Current));
                    }
                    if ((unit & unitsSoFar) != 0)
                    {
                        return(RepeatedUnit(valueCursor, valueCursor.Current));
                    }

                    // This handles putting months before years, for example. Less significant units
                    // have higher integer representations.
                    if (unit < unitsSoFar)
                    {
                        return(MisplacedUnit(valueCursor, valueCursor.Current));
                    }
                    // The result of checking "there aren't any time units in this unit" should be
                    // equal to "we're still in the date part".
                    if ((unit & PeriodUnits.AllTimeUnits) == 0 != inDate)
                    {
                        return(MisplacedUnit(valueCursor, valueCursor.Current));
                    }
                    builder[unit] = unitValue;
                    unitsSoFar   |= unit;
                }
                return(ParseResult <Period> .ForValue(builder.Build()));
            }
예제 #25
0
 public ParseResult <Offset> Parse(string text) => text == "Z" ? ParseResult <Offset> .ForValue(Offset.Zero) : fullPattern.Parse(text);