New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

timelength

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

timelength

A flexible python duration parser designed for human readable lengths of time.

  • 3.0.0
  • PyPI
  • Socket score

Maintainers
1

timelength

A flexible python duration parser designed for human readable lengths of time, including long form durations such as 1 day, 5 hours, and 30 seconds, short form durations such as 1d5h30s, numerals such as twelve hours, HHMMSS such as 12:30:15, and a mix thereof such as 1 day 5h 30s.

Installation

timelength can be installed via pip:

pip install timelength

Or added to your project via poetry:

poetry add timelength

Usage

Basic

Import TimeLength and pass it a string to evaluate. As long as a single valid result is found the parsing will be considered a success regardless of any invalid content included in the input.

from datetime import datetime, timezone
from timelength import TimeLength

tl = TimeLength("1.27 hours and 23 miles")
print(tl.result.success)
# True
print(tl.result.seconds)
# 4572.0
print(tl.to_minutes(max_precision=3))
# 76.2
print(tl.result.delta)
# 1:16:12
print(tl.ago(base=datetime(2025, 1, 1, 0, 0, 0, 0, timezone.utc)))
# 2024-12-31 22:43:48+00:00
print(tl.hence(base=datetime(2025, 1, 1, 0, 0, 0, 0, timezone.utc)))
# 2025-01-01 01:16:12+00:00
print(tl.result.invalid)
# ((23.0, FailureFlags.LONELY_VALUE), ('miles', FailureFlags.UNKNOWN_TERM))
print(tl.result.valid)
# ((1.27, Scale(scale=3600.0, ...)),)

Advanced

Control can be increased by making use of FailureFlags and ParserSettings. Both are passed to whichever Locale you are parsing with which is then passed to the TimeLength to be parsed.

FailureFlags is an IntFlag enum which holds all the currently possible reasons for a parse to fail. The default is FailureFlags.NONE which means as long as a single value is parsed, it will be considered a success. To achieve the opposite behavior, FailureFlags.ALL will cause the parsing to be considered a failure if any FailureFlags are detected, even if a valid value was parsed. See below for a full list.

ParserSettings is an object with various parsing settings. See below for a full list of options along with the default values.

In the first part of the example below, the flags are set to (FailureFlags.LONELY_VALUE | FailureFlags.UNKNOWN_TERM), which means as long as a single valid item is found, the parsing will only be considered a failure if FailureFlags.LONELY_VALUE or FailureFlags.UNKNOWN_TERM show up in the invalid items. As 19 is included in the input without an accompanying scale, it is considered a LONELY_VALUE and thus invalid.

In the second part, the flags are reset, meaning nothing will cause parsing to be considered a failure as long as a single valid item is found. Additionally, the settings are updated such that duplicate scales are not allowed to be parsed. Due to this, 5m from the input is added to the invalid items as a DUPLICATE_SCALE due to the preceding 35m. Also, 19 is still in the invalid items as a LONELY_VALUE. Despite this, since the flags is set to FailureFlags.NONE and since 3.5d, 35m is successfully parsed, the overall parsing is considered a success.

from timelength import English, FailureFlags, ParserSettings, TimeLength

flags = (FailureFlags.LONELY_VALUE | FailureFlags.UNKNOWN_TERM)
locale = English(flags=flags)
tl = TimeLength("3.5d, 35m, 5m, 19", locale=locale)
print(tl.result.success)
# False
print(tl.result.invalid)
# ((19.0, FailureFlags.LONELY_VALUE),)
print(tl.result.valid)
# ((3.5, Scale(scale=86400.0, ...)), (35.0, Scale(scale=60.0, ...)), (5.0, Scale(scale=60.0, ...)))

flags = FailureFlags.NONE
settings = ParserSettings(allow_duplicate_scales=False)
locale.flags = flags
locale.settings = settings
tl.parse()
print(tl.result.success)
# True
print(tl.result.invalid)
# (('5m', FailureFlags.DUPLICATE_SCALE), (19.0, FailureFlags.LONELY_VALUE))
print(tl.result.valid)
# ((3.5, Scale(scale=86400.0, ...)), (35.0, Scale(scale=60.0, ...)))

To put it simply, FailureFlags is used to determine if an item that is in the invalid items tuple should invalidate the parsing as a whole, whereas ParserSettings is used to determine if certain customizable situations should even result in an item being added to the invalid items tuple to begin with.

FailureFlags

The members of the FailureFlags IntEnum are:

  • NONE — No failures will cause parsing to fail.
  • ALL — Any failure will cause parsing to fail.
  • MALFORMED_CONTENT — The fallback when something that shouldn't have happened, happened.
  • UNKNOWN_TERM — The parsed value was not recognized from the terms or symbols in the config.
    • Ex: 1 mile
  • MALFORMED_DECIMAL — Multiple decimals were attempted within a singular decimal segment.
    • Ex: 1.2.3min
  • MALFORMED_THOUSAND — A thousand segment was attempted but did not have a leading number or a proper number of digits following a thousand separator.
    • Ex: ,234, 1,23, or 1,2345
  • MALFORMED_FRACTION — A fraction was attempted but had more than 2 values, a missing value, or was not formatted correctly.
    • Ex: 1/2/3, /2, or 1+ / +2
  • MALFORMED_HHMMSS — An HH:MM:SS was attempted but had more segments than enabled scales or was not formatted correctly.
    • Ex: 1:2:3:4:5:6:7:8:9:10:11:12:13:14:15 or 1:15:26:
  • LONELY_VALUE — A value was parsed with no paired scale.
    • Ex: one minute and twenty
  • LONELY_SCALE — A scale was parsed with no paired value.
    • Ex: 2 minutes and hours
  • DUPLICATE_SCALE — The same scale was parsed multiple times.
    • Ex: 1min 5:23 or 1min 5 minutes and 23 seconds
  • CONSECUTIVE_CONNECTOR — More than the allowed number of connectors were parsed in a row.
    • Ex: 1h 2min
  • CONSECUTIVE_SEGMENTOR — More than the allowed number of segmentors were parsed in a row.
    • Ex: 1h,, 2min
  • CONSECUTIVE_SPECIAL — More than the allowed number of special characters were parsed in a row.
    • Ex: 1h 2min!!
  • MISPLACED_ALLOWED_TERM — An allowed term was found in the middle of a segment/sentence.
    • Ex: 1!h 2min
  • MISPLACED_SPECIAL — A special character was found in the middle of a segment/sentence.
    • Ex: 1, /2
  • UNUSED_OPERATION — A term of the operation numeral was parsed but unused on any values.
    • Ex: 2 min of
  • AMBIGUOUS_MULTIPLIERS — More than one multiplier was parsed for a single segment which may be ambiguous.
    • Ex: half of a quarter of two minutes and 30s

ParserSettings

  • assume_scale: Literal["LAST", "SINGLE", "NEVER"] = "SINGLE" — How to handle no scale being provided.
    • LAST will assume seconds only for the last value if no scale is provided for it.
    • SINGLE will only assume seconds when a single input is provided.
    • NEVER will never assume seconds when no scale is provided.
  • limit_allowed_terms: bool = True — Prevent terms from the allowed_terms list in the config from being used in the middle of a segment, thus interrupting a value/scale pair.
    • The affected segment will become abandoned and added to the invalid list.
    • The terms may still be used at the beginning or end of a segment/sentence.
    • If False, The terms will be ignored (within other limitations) and not effect parsing.
  • allow_duplicate_scales: bool = True — Allow scales to be parsed multiple times, stacking their values.
    • If False, the first scale will be used and subsequent duplicates will be added to the invalid list.
  • allow_thousands_extra_digits: bool = False — Allow thousands to be parsed with more than three digits following a thousand delimiter.
    • Ex: 1,2345 will be interpreted as 12,345.
  • allow_thousands_lacking_digits: bool = False — Allow a number to be parsed with less than three digits following a thousand delimiter.
    • Ex: 1,23 will be interpreted as 123.
  • allow_decimals_lacking_digits: bool = True — Allow decimals to be parsed with no number following the decimal delimiter.
    • Ex: 1. will be interpreted as 1.0.

Guess the Locale

If you're unsure of which locale the input will belong to, you can attempt to guess the locale from the available options.

from timelength import English, Spanish, Guess, TimeLength

guess = Guess()
tl = TimeLength("5 minutos", locale=guess)
print(tl.locale)
# Spanish
print(tl.result.success)
# True
print(tl.result.valid)
# ((5.0, Scale(scale=60.0, ...)))

tl.content = "5 minutes"
tl.parse(guess_locale=guess) # or `guess_locale=True`
print(tl.locale)
# English
print(tl.result.success)
# True
print(tl.result.valid)
# ((5.0, Scale(scale=60.0, ...)))

To guess the locale you can pass in an instantiated Guess either on creation of the TimeLength or to the parse() function on subsequent calls. Doing so will save locale instantiations as with each new Guess created, each locale available is instantiated in Guess().locales. Alternatively, for parse(), you can pass True for guess_locale and a new Guess will be instantiated automatically.

Guessing works by parsing with each locale. The one with the least invalid results is considered the correct locale. Ties are broken by most valid followed by alphabetically.

Any flags or settings passed to the Guess will be passed on to each locale. If none are provided, the defaults in each locale's configs are used. If you need to specify flags or settings per locale, you can directly access Guess().locales, which is a list of instantiated versions of all of the currently available locales.

If you have any custom locales you would like to be included in the possible results, append to Guess().locales before passing it to the TimeLength or parse() function.

Operations & Comparisons

TimeLength objects support various arithmetic operations and comparisons between each other, datetimes, timedelta, and numbers. The supported options are:

  1. Addition

    • TimeLength + TimeLength or timedelta or number -> TimeLength
    • datetime or timedelta + TimeLength -> datetime or timedelta
  2. Subtraction

    • TimeLength - TimeLength or timedelta or number -> TimeLength
    • datetime or timedelta - TimeLength -> datetime or timedelta
  3. Multiplication

    • TimeLength * number -> TimeLength
    • number * TimeLength -> TimeLength
  4. Division

    • TimeLength / TimeLength or timedelta or number -> TimeLength or float
    • timedelta / TimeLength -> float
  5. Floor Division

    • TimeLength // TimeLength or timedelta or number -> TimeLength or float
    • timedelta // TimeLength -> float
  6. Modulo

    • TimeLength % TimeLength or timedelta -> TimeLength
    • timedelta % TimeLength -> timedelta
  7. Divmod

    • divmod(TimeLength, TimeLength or timedelta) -> tuple[float, TimeLength]
    • divmod(timedelta, TimeLength) -> tuple[float, timedelta]
  8. Power

    • TimeLength ** number -> TimeLength (optionally moduloed by TimeLength or timedelta)
  9. Comparisons

    • TimeLength > TimeLength or timedelta -> bool
    • TimeLength >= TimeLength or timedelta -> bool
    • TimeLength < TimeLength or timedelta -> bool
    • TimeLength <= TimeLength or timedelta -> bool
    • TimeLength == TimeLength or timedelta -> bool
    • TimeLength != TimeLength or timedelta -> bool
  10. Other Operations

    • abs(TimeLength) -> TimeLength (returns self unchanged as TimeLength is an absolute measurement)
    • +TimeLength -> TimeLength (returns self unchanged)
    • -TimeLength -> TimeLength (returns self unchanged)
    • bool(TimeLength) -> bool (returns True if parsing succeeded, otherwise False)
    • len(TimeLength) -> int (returns the length of self.content)

Example Inputs

  • 1m
  • 1min
  • 1 Minute
  • 1m and 2 SECONDS
  • 3h, 2 min, 3sec
  • 1.2d
  • 1,234s
  • one hour
  • twenty-two hours and thirty five minutes
  • half of a day
  • 1/2 of a day
  • 1/4 hour
  • 1 Day, 2:34:12
  • 1:2:34:12
  • 1:5:13:27:22

Usage Notes

  1. Numerals
    • Segmentors are ignored when parsing numerals in order to achieve consistency over ambiguity.
  2. Multipliers
    • A single multiplier (ex: half) is allowed per segment (value + scale). This is due to the ambiguity introduced when more than one multiplier is used. May be revisited in the future if a good way to handle this ambiguity is found.
  3. HHMMSS
    • It is not strictly adherent to typical HH:MM:SS standards. Any parsable numbers work in each slot, whether they are single digits, multiple digits, or include decimals.
      • For example, 2.56:27/3:270:19.2231 is a valid input in place of 2.56 days, 9 hours, 270 minutes, and 19.2231 seconds.
      • It also accepts a single connector, such as a space, between deliminators. Example: 2: 6: 3.
    • Supports up to as many segments as there are scales defined, including custom scales (10 default, Millisecond to Century).
      • The segments are parsed in reverse order, so smallest to largest (the order defined in the config and therefore the order loaded into the Locale, may differ for custom defined Scales) to ensure the correct scales are applied.
        • EXCEPTION: The default base from which HH:MM:SS starts at is Second. Any scales (typically of lesser value) listed prior in the config, or appended to the scales list before Second in the case of custom scales, will not be utilized unless Second is disabled or as many HH:MM:SS segments are parsed as there are scales defined.
        • 12:30:15 is 12 hours, 30 minutes, and 15 seconds, NOT 12 minutes, 30 seconds, and 15 milliseconds.
        • 1:10:100:12:52:30:24:60:60:1000 will make use of Century to Millisecond.

Supported Locales

  1. English
  2. Spanish
  3. Basic Custom
    • Copy & modify an existing config with new terms as long as your new Locale follows the existing config parser's grammar structure
  4. Advanced Custom
    • Write your own parsing logic if your Locale's grammar structure differs too drastically (PRs welcome)

Customization

timelength allows for customizing the parsing behavior through JSON configuration. To get started, copy an existing locale JSON in timelength/locales/. The custom JSON may be placed anywhere.

Ensure the JSON being used is from a trusted source, as the parser is loaded dynamically based on the file specified in the JSON. This could allow for unintended code execution if an unsafe config is loaded.

Valid JSONs must include the following keys:

  • connectors
    • Characters/words that join two parts of the same segment.
    • Must have at least one value.
  • segmentors
    • Characters/words that separate an input into segments.
    • Must have at least one value.
  • allowed_terms
    • Characters or terms that won't be categorized as an invalid input. If sent multiple times in a row (ex: !!), they will still be marked as invalid. If ParserSettings().limit_allowed_terms is set then these characters won't be allowed in the middle of a segment (ex: 1!min) and will cause the segment progress to reset.
  • hhmmss_delimiters
    • Characters used to form HH:MM:SS segments. Can't have overlap with decimal_delimiters, thousand_delimiters, or fraction_delimiters.
  • decimal_delimiters
    • Characters used to separate decimals from digits. Can't have overlap with hhmmss_delimiters, thousand_delimiters, or fraction_delimiters.
  • thousand_delimiters
    • Characters used to break up large numbers. Can't have overlap with hhmmss_delimiters, decimal_delimiters, or fraction_delimiters.
  • fraction_delimiters
    • Characters used to form fractions. Can't have overlap with hhmmss_delimiters, decimal_delimiters, or thousand_delimiters.
  • parser_file
    • The name of this locale's parser file (extension included) located in timelength/parsers/, or the path to the parser file if stored elsewhere.
    • Ensure only a trusted file is used as this could allow unintended code execution.
    • The internal parser method must share a name with the file. Example: parser_one.py and def parser_one().
  • scales
    • Periods of time. The defaults are millisecond, second, minute, hour, day, week, month, year, decade, and century. Default scales must be present. At least one scale must be valid and enabled. Custom scales can be added following the format of the others. The following keys must be present and populated:
      • scale
        • The number of seconds this scale represents.
      • singular
        • The lowercase singular form of this scale.
      • plural
        • The lowercase plural form of this scale.
      • terms
        • All terms that could be parsed as this scale. Accents and other NFKD markings should not be present as they are filtered from the user input.
    • The following key is optional to disable a scale without removing it from the config:
      • enabled
        • A bool indicating if the scale is enabled or not.
  • numerals
    • Word forms of numbers. May be populated or left empty. Each element must itself have the following keys, even if their contents are not used:
      • type
        • The numeral type.
      • value
        • The numerical value of this numeral.
      • terms
        • Characters/words that parse to this numeral's value.
  • flags
    • A list of FailureFlags which will cause parsing to be considered a failure. Values should be upper cased. This is optional and can be passed to the locale dynamically instead. See a full list of options in the FailureFlags section.
  • settings
    • A dictionary of options to modify parsing behavior. Each key should have a string or boolean value as appropriate. This is optional and can be passed to the locale dynamically instead. See a full list of options in the ParserSettings section.
  • extra_data
    • Any data a parser needs that is not already covered. May be populated or left empty. The locale loads this into a Locale().extra_data attribute, leaving the parser to utilize it.

Once your custom JSON is filled out, you can use it as follows:

from timelength import TimeLength, Locale

output = TimeLength("30 minutes", locale = Locale("path/to/config.json"))

If all goes well, the parsing will succeed, and if not, an error will point you in the right direction.

Keywords

FAQs


Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc