/// <summary> /// Creates a character handler to handle the "fraction of a second" specifier (f or F). /// </summary> internal static CharacterHandler <TResult, TBucket> CreateFractionHandler <TResult, TBucket> (int maxCount, NodaFunc <TResult, int> getter, NodaAction <TBucket, int> setter) where TBucket : ParseBucket <TResult> { return((pattern, builder) => { char patternCharacter = pattern.Current; int count = pattern.GetRepeatCount(maxCount); builder.AddField(PatternFields.FractionalSeconds, pattern.Current); builder.AddParseAction((str, bucket) => { int fractionalSeconds; // If the pattern is 'f', we need exactly "count" digits. Otherwise ('F') we need // "up to count" digits. if (!str.ParseFraction(count, maxCount, out fractionalSeconds, patternCharacter == 'f')) { return ParseResult <TResult> .MismatchedNumber(new string(patternCharacter, count)); } // No need to validate the value - we've got an appropriate number of digits, so the range is guaranteed. setter(bucket, fractionalSeconds); return null; }); if (patternCharacter == 'f') { builder.AddFormatRightPad(count, maxCount, getter); } else { builder.AddFormatRightPadTruncate(count, maxCount, getter); } }); }
/// <summary> /// Adds parse and format actions for an "negative only" sign. /// </summary> /// <param name="signSetter">Action to take when to set the given sign within the bucket</param> /// <param name="nonNegativePredicate">Predicate to detect whether the value being formatted is non-negative</param> public void AddNegativeOnlySign(NodaAction <TBucket, bool> signSetter, NodaFunc <TResult, bool> nonNegativePredicate) { string negativeSign = formatInfo.NegativeSign; string positiveSign = formatInfo.PositiveSign; AddParseAction((str, bucket) => { if (str.Match(negativeSign)) { signSetter(bucket, false); return(null); } if (str.Match(positiveSign)) { return(ParseResult <TResult> .PositiveSignInvalid); } signSetter(bucket, true); return(null); }); AddFormatAction((value, builder) => { if (!nonNegativePredicate(value)) { builder.Append(negativeSign); } }); }
/// <summary> /// Creates a character handler for the calendar specifier (c). /// </summary> internal static CharacterHandler <TResult, TBucket> CreateCalendarHandler <TResult, TBucket> (NodaFunc <TResult, CalendarSystem> getter, NodaAction <TBucket, CalendarSystem> setter) where TBucket : ParseBucket <TResult> { return((pattern, builder) => { builder.AddField(PatternFields.Calendar, pattern.Current); builder.AddParseAction((cursor, bucket) => { // TODO(V2.0): (Breaking change, although undocumented.) Potentially make this case-sensitive // as we're parsing IDs. foreach (var id in CalendarSystem.Ids) { if (cursor.MatchCaseInsensitive(id, NodaFormatInfo.InvariantInfo.CompareInfo, true)) { setter(bucket, CalendarSystem.ForId(id)); return null; } } return ParseResult <TResult> .NoMatchingCalendarSystem; }); builder.AddFormatAction((value, sb) => sb.Append(getter(value).Id)); }); }
/// <summary> /// Creates a character handler for the day specifier (d). /// </summary> internal static CharacterHandler <TResult, TBucket> CreateDayHandler <TResult, TBucket> (NodaFunc <TResult, int> dayOfMonthGetter, NodaFunc <TResult, int> dayOfWeekGetter, NodaAction <TBucket, int> dayOfMonthSetter, NodaAction <TBucket, int> dayOfWeekSetter) where TBucket : ParseBucket <TResult> { return((pattern, builder) => { int count = pattern.GetRepeatCount(4); PatternFields field; switch (count) { case 1: case 2: field = PatternFields.DayOfMonth; // Handle real maximum value in the bucket builder.AddParseValueAction(count, 2, pattern.Current, 1, 99, dayOfMonthSetter); builder.AddFormatAction((value, sb) => FormatHelper.LeftPad(dayOfMonthGetter(value), count, sb)); break; case 3: case 4: field = PatternFields.DayOfWeek; var format = builder.FormatInfo; IList <string> textValues = count == 3 ? format.ShortDayNames : format.LongDayNames; builder.AddParseLongestTextAction(pattern.Current, dayOfWeekSetter, format.CompareInfo, textValues); builder.AddFormatAction((value, sb) => sb.Append(textValues[dayOfWeekGetter(value)])); break; default: throw new InvalidOperationException("Invalid count!"); } builder.AddField(field, pattern.Current); }); }
internal T ExtractSingleValue <T>(NodaFunc <DateTimeZoneReader, T> readerFunction, IList <string> stringPool) { using (var stream = CreateStream()) { return(readerFunction(new DateTimeZoneReader(stream, stringPool))); } }
internal SteppedPatternBuilder(NodaFormatInfo formatInfo, NodaFunc <TBucket> bucketProvider) { this.formatInfo = formatInfo; formatActions = new List <NodaAction <TResult, StringBuilder> >(); parseActions = new List <ParseAction>(); this.bucketProvider = bucketProvider; }
internal Cache(int size, NodaFunc <TKey, TValue> valueFactory, IEqualityComparer <TKey> keyComparer) { this.size = size; this.valueFactory = valueFactory; this.dictionary = new Dictionary <TKey, TValue>(keyComparer); this.keyList = new LinkedList <TKey>(); }
public SteppedPattern(NodaAction <TResult, StringBuilder> formatActions, ParseAction[] parseActions, NodaFunc <TBucket> bucketProvider, PatternFields usedFields) { this.formatActions = formatActions; this.parseActions = parseActions; this.bucketProvider = bucketProvider; this.usedFields = usedFields; }
private FixedFormatInfoPatternParser <T> EnsureFixedFormatInitialized <T>(ref FixedFormatInfoPatternParser <T> field, NodaFunc <IPatternParser <T> > patternParserFactory) { lock (fieldLock) { if (field == null) { field = new FixedFormatInfoPatternParser <T>(patternParserFactory(), this); } return(field); } }
private IPartialPattern <Offset> CreateGeneralPattern(NodaFormatInfo formatInfo) { var patterns = new List <IPartialPattern <Offset> >(); foreach (char c in "flms") { patterns.Add(ParsePartialPattern(c.ToString(), formatInfo)); } NodaFunc <Offset, IPartialPattern <Offset> > formatter = value => PickGeneralFormatter(value, patterns); return(new CompositePattern <Offset>(patterns, formatter)); }
/// <summary> /// Creates a character handler for a dot (period) or comma, which have the same meaning. /// Formatting always uses a dot, but parsing will allow a comma instead, to conform with /// ISO-8601. This is *not* culture sensitive. /// </summary> internal static CharacterHandler <TResult, TBucket> CreateCommaDotHandler <TResult, TBucket> (int maxCount, NodaFunc <TResult, int> getter, NodaAction <TBucket, int> setter) where TBucket : ParseBucket <TResult> { return((pattern, builder) => { // Note: Deliberately *not* using the decimal separator of the culture - see issue 21. // If the next part of the pattern is an F, then this decimal separator is effectively optional. // At parse time, we need to check whether we've matched the decimal separator. If we have, match the fractional // seconds part as normal. Otherwise, we continue on to the next parsing token. // At format time, we should always append the decimal separator, and then append using PadRightTruncate. if (pattern.PeekNext() == 'F') { pattern.MoveNext(); int count = pattern.GetRepeatCount(maxCount); builder.AddField(PatternFields.FractionalSeconds, pattern.Current); builder.AddParseAction((valueCursor, bucket) => { // If the next token isn't a dot or comma, we assume // it's part of the next token in the pattern if (!valueCursor.Match('.') && !valueCursor.Match(',')) { return null; } // If there *was* a decimal separator, we should definitely have a number. int fractionalSeconds; // Last argument is false because we don't need *all* the digits to be present if (!valueCursor.ParseFraction(count, maxCount, out fractionalSeconds, false)) { return ParseResult <TResult> .MismatchedNumber(new string('F', count)); } // No need to validate the value - we've got one to three digits, so the range 0-999 is guaranteed. setter(bucket, fractionalSeconds); return null; }); builder.AddFormatAction((localTime, sb) => sb.Append('.')); builder.AddFormatRightPadTruncate(count, maxCount, getter); } else { builder.AddParseAction((str, bucket) => str.Match('.') || str.Match(',') ? null : ParseResult <TResult> .MismatchedCharacter(';')); builder.AddFormatAction((value, sb) => sb.Append('.')); } }); }
/// <summary> /// Creates a character handler for the year specifier (y). /// </summary> internal static CharacterHandler <TResult, TBucket> CreateYearHandler <TResult, TBucket> (NodaFunc <TResult, int> centuryGetter, NodaFunc <TResult, int> yearGetter, NodaAction <TBucket, int> setter) where TBucket : ParseBucket <TResult> { return((pattern, builder) => { int count = pattern.GetRepeatCount(5); builder.AddField(PatternFields.Year, pattern.Current); switch (count) { case 1: case 2: builder.AddParseValueAction(count, 2, 'y', -99, 99, setter); builder.AddFormatAction((value, sb) => FormatHelper.LeftPad(centuryGetter(value), count, sb)); // Just remember that we've set this particular field. We can't set it twice as we've already got the Year flag set. builder.AddField(PatternFields.YearTwoDigits, pattern.Current); break; case 3: // Maximum value will be determined later. // Three or more digits (ick). builder.AddParseValueAction(3, 5, 'y', -99999, 99999, setter); builder.AddFormatAction((value, sb) => FormatHelper.LeftPad(yearGetter(value), 3, sb)); break; case 4: // Left-pad to 4 digits when formatting. Parse either exactly 4 or up to 5 digits depending // on the *next* character of the padding. bool parseExactly4 = CheckIfNextCharacterMightBeDigit(pattern); builder.AddParseValueAction(4, parseExactly4 ? 4 : 5, 'y', -99999, 99999, setter); builder.AddFormatAction((value, sb) => FormatHelper.LeftPad(yearGetter(value), 4, sb)); break; case 5: // Maximum value will be determined later. // Note that the *exact* number of digits are required; not just "at least count". builder.AddParseValueAction(count, count, 'y', -99999, 99999, setter); builder.AddFormatAction((value, sb) => FormatHelper.LeftPad(yearGetter(value), 5, sb)); break; default: throw new InvalidOperationException("Bug in Noda Time; invalid count for year went undetected."); } }); }
/// <summary> /// Creates a character handler for the era specifier (g). /// </summary> internal static CharacterHandler <TResult, TBucket> CreateEraHandler <TResult, TBucket> (NodaFunc <TResult, Era> eraFromValue, NodaFunc <TBucket, LocalDatePatternParser.LocalDateParseBucket> dateBucketFromBucket) where TBucket : ParseBucket <TResult> { return((pattern, builder) => { pattern.GetRepeatCount(2); builder.AddField(PatternFields.Era, pattern.Current); var formatInfo = builder.FormatInfo; // Note: currently the count is ignored. More work needed to determine whether abbreviated era names should be used for just "g". builder.AddParseAction((cursor, bucket) => { var dateBucket = dateBucketFromBucket(bucket); return dateBucket.ParseEra <TResult>(formatInfo, cursor); }); builder.AddFormatAction((value, sb) => sb.Append(formatInfo.GetEraPrimaryName(eraFromValue(value)))); }); }
/// <summary> /// Creates a character handler for the month-of-year specifier (M). /// </summary> internal static CharacterHandler <TResult, TBucket> CreateMonthOfYearHandler <TResult, TBucket> (NodaFunc <TResult, int> numberGetter, NodaAction <TBucket, int> textSetter, NodaAction <TBucket, int> numberSetter) where TBucket : ParseBucket <TResult> { return((pattern, builder) => { int count = pattern.GetRepeatCount(4); PatternFields field; switch (count) { case 1: case 2: field = PatternFields.MonthOfYearNumeric; // Handle real maximum value in the bucket builder.AddParseValueAction(count, 2, pattern.Current, 0, 99, numberSetter); builder.AddFormatAction((value, sb) => FormatHelper.LeftPad(numberGetter(value), count, sb)); break; case 3: case 4: field = PatternFields.MonthOfYearText; var format = builder.FormatInfo; IList <string> nonGenitiveTextValues = count == 3 ? format.ShortMonthNames : format.LongMonthNames; IList <string> genitiveTextValues = count == 3 ? format.ShortMonthGenitiveNames : format.LongMonthGenitiveNames; if (nonGenitiveTextValues == genitiveTextValues) { builder.AddParseLongestTextAction(pattern.Current, textSetter, format.CompareInfo, nonGenitiveTextValues); } else { builder.AddParseLongestTextAction(pattern.Current, textSetter, format.CompareInfo, genitiveTextValues, nonGenitiveTextValues); } // Hack: see below builder.AddFormatAction(new MonthFormatActionHolder <TResult, TBucket>(format, count, numberGetter).DummyMethod); break; default: throw new InvalidOperationException("Invalid count!"); } builder.AddField(field, pattern.Current); }); }
private static void HandleHalfAmPmDesignator <TResult, TBucket> (int count, string specifiedDesignator, int specifiedDesignatorValue, NodaFunc <TResult, int> hourOfDayGetter, NodaAction <TBucket, int> amPmSetter, SteppedPatternBuilder <TResult, TBucket> builder) where TBucket : ParseBucket <TResult> { CompareInfo compareInfo = builder.FormatInfo.CompareInfo; if (count == 1) { string abbreviation = specifiedDesignator.Substring(0, 1); builder.AddParseAction((str, bucket) => { int value = str.MatchCaseInsensitive(abbreviation, compareInfo, true) ? specifiedDesignatorValue : 1 - specifiedDesignatorValue; amPmSetter(bucket, value); return(null); }); builder.AddFormatAction((value, sb) => { // Only append anything if it's the non-empty designator. if (hourOfDayGetter(value) / 12 == specifiedDesignatorValue) { sb.Append(specifiedDesignator[0]); } }); return; } builder.AddParseAction((str, bucket) => { int value = str.MatchCaseInsensitive(specifiedDesignator, compareInfo, true) ? specifiedDesignatorValue : 1 - specifiedDesignatorValue; amPmSetter(bucket, value); return(null); }); builder.AddFormatAction((value, sb) => { // Only append anything if it's the non-empty designator. if (hourOfDayGetter(value) / 12 == specifiedDesignatorValue) { sb.Append(specifiedDesignator); } }); }
/// <summary> /// Adds parse and format actions for a mandatory positive/negative sign. /// </summary> /// <param name="signSetter">Action to take when to set the given sign within the bucket</param> /// <param name="nonNegativePredicate">Predicate to detect whether the value being formatted is non-negative</param> public void AddRequiredSign(NodaAction <TBucket, bool> signSetter, NodaFunc <TResult, bool> nonNegativePredicate) { string negativeSign = formatInfo.NegativeSign; string positiveSign = formatInfo.PositiveSign; AddParseAction((str, bucket) => { if (str.Match(negativeSign)) { signSetter(bucket, false); return(null); } if (str.Match(positiveSign)) { signSetter(bucket, true); return(null); } return(ParseResult <TResult> .MissingSign); }); AddFormatAction((value, sb) => sb.Append(nonNegativePredicate(value) ? positiveSign : negativeSign)); }
internal static ParseResult <T> ForException(NodaFunc <Exception> exceptionProvider) { return(new ParseResult <T>(exceptionProvider, false)); }
/// <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)); }
internal MonthFormatActionHolder(NodaFormatInfo formatInfo, int count, NodaFunc <TResult, int> getter) { this.count = count; this.formatInfo = formatInfo; this.getter = getter; }
internal CompositePattern(IEnumerable <IPartialPattern <T> > parsePatterns, NodaFunc <T, IPartialPattern <T> > formatPatternPicker) { this.parsePatterns = new List <IPartialPattern <T> >(parsePatterns); this.formatPatternPicker = formatPatternPicker; }
internal PatternBclSupport(string defaultFormatPattern, NodaFunc <NodaFormatInfo, FixedFormatInfoPatternParser <T> > patternParser) { this.patternParser = patternParser; this.defaultFormatPattern = defaultFormatPattern; }
internal void AddFormatRightPadTruncate(int width, int scale, NodaFunc <TResult, int> selector) { AddFormatAction((value, sb) => FormatHelper.RightPadTruncate(selector(value), width, scale, formatInfo.DecimalSeparator, sb)); }
internal void AddFormatRightPad(int width, int scale, NodaFunc <TResult, int> selector) { AddFormatAction((value, sb) => FormatHelper.RightPad(selector(value), width, scale, sb)); }
internal void AddFormatLeftPad(int count, NodaFunc <TResult, int> selector) { AddFormatAction((value, sb) => FormatHelper.LeftPad(selector(value), count, sb)); }
private static ParseResult <T> ForInvalidValue(NodaFunc <Exception> exceptionProvider) { return(new ParseResult <T>(exceptionProvider, true)); }
internal static CharacterHandler <TResult, TBucket> CreateAmPmHandler <TResult, TBucket> (NodaFunc <TResult, int> hourOfDayGetter, NodaAction <TBucket, int> amPmSetter) where TBucket : ParseBucket <TResult> { return((pattern, builder) => { int count = pattern.GetRepeatCount(2); builder.AddField(PatternFields.AmPm, pattern.Current); string amDesignator = builder.FormatInfo.AMDesignator; string pmDesignator = builder.FormatInfo.PMDesignator; // If we don't have an AM or PM designator, we're nearly done. Set the AM/PM designator // to the special value of 2, meaning "take it from the template". if (amDesignator == "" && pmDesignator == "") { builder.AddParseAction((str, bucket) => { amPmSetter(bucket, 2); return null; }); return; } // Odd scenario (but present in af-ZA for .NET 2) - exactly one of the AM/PM designator is valid. // Delegate to a separate method to keep this clearer... if (amDesignator == "" || pmDesignator == "") { int specifiedDesignatorValue = amDesignator == "" ? 1 : 0; string specifiedDesignator = specifiedDesignatorValue == 1 ? pmDesignator : amDesignator; HandleHalfAmPmDesignator(count, specifiedDesignator, specifiedDesignatorValue, hourOfDayGetter, amPmSetter, builder); return; } CompareInfo compareInfo = builder.FormatInfo.CompareInfo; // Single character designator if (count == 1) { // It's not entirely clear whether this is the right thing to do... there's no nice // way of providing a single-character case-insensitive match. string amFirst = amDesignator.Substring(0, 1); string pmFirst = pmDesignator.Substring(0, 1); builder.AddParseAction((str, bucket) => { if (str.MatchCaseInsensitive(amFirst, compareInfo, true)) { amPmSetter(bucket, 0); return null; } if (str.MatchCaseInsensitive(pmFirst, compareInfo, true)) { amPmSetter(bucket, 1); return null; } return ParseResult <TResult> .MissingAmPmDesignator; }); builder.AddFormatAction((value, sb) => sb.Append(hourOfDayGetter(value) > 11 ? pmDesignator[0] : amDesignator[0])); return; } // Full designator builder.AddParseAction((str, bucket) => { // Could use the "match longest" approach, but with only two it feels a bit silly to build a list... bool pmLongerThanAm = pmDesignator.Length > amDesignator.Length; string longerDesignator = pmLongerThanAm ? pmDesignator : amDesignator; string shorterDesignator = pmLongerThanAm ? amDesignator : pmDesignator; int longerValue = pmLongerThanAm ? 1 : 0; if (str.MatchCaseInsensitive(longerDesignator, compareInfo, true)) { amPmSetter(bucket, longerValue); return null; } if (str.MatchCaseInsensitive(shorterDesignator, compareInfo, true)) { amPmSetter(bucket, 1 - longerValue); return null; } return ParseResult <TResult> .MissingAmPmDesignator; }); builder.AddFormatAction((value, sb) => sb.Append(hourOfDayGetter(value) > 11 ? pmDesignator : amDesignator)); }); }
/// <summary> /// Adds a character which must be matched exactly when parsing, and appended directly when formatting. /// </summary> internal void AddLiteral(char expectedChar, NodaFunc <char, ParseResult <TResult> > failureSelector) { AddParseAction((str, bucket) => str.Match(expectedChar) ? null : failureSelector(expectedChar)); AddFormatAction((value, builder) => builder.Append(expectedChar)); }
private ParseResult(NodaFunc <Exception> exceptionProvider, bool continueWithMultiple) { this.exceptionProvider = exceptionProvider; this.continueWithMultiple = continueWithMultiple; }
/// <summary> /// Returns a handler for a zero-padded purely-numeric field specifier, such as "seconds", "minutes", "24-hour", "12-hour" etc. /// </summary> /// <param name="maxCount">Maximum permissable count (usually two)</param> /// <param name="field">Field to remember that we've seen</param> /// <param name="minValue">Minimum valid value for the field (inclusive)</param> /// <param name="maxValue">Maximum value value for the field (inclusive)</param> /// <param name="getter">Delegate to retrieve the field value when formatting</param> /// <param name="setter">Delegate to set the field value into a bucket when parsing</param> /// <returns>The pattern parsing failure, or null on success.</returns> internal static CharacterHandler <TResult, TBucket> HandlePaddedField(int maxCount, PatternFields field, int minValue, int maxValue, NodaFunc <TResult, int> getter, NodaAction <TBucket, int> setter) { return((pattern, builder) => { int count = pattern.GetRepeatCount(maxCount); builder.AddField(field, pattern.Current); builder.AddParseValueAction(count, maxCount, pattern.Current, minValue, maxValue, setter); builder.AddFormatLeftPad(count, getter); }); }