1
votes

Analyse pratique des chaînes de temps en python

J'ai besoin de calculer la durée entre une date spécifique et maintenant pour le nettoyage d'index elasticsearch. Mon travail fonctionnera en python. J'ai un fichier de configuration:

indices:
  - name: test
    template: raw*
    liveLength: 1d

Comment analyser la chaîne "1d" ou "2m" à un intervalle de temps valide pour calculer la durée à partir d'une date spécifique à partir du champ liveLength?

p>


0 commentaires

3 Réponses :


0
votes

J'ai trouvé le code sur GitHub:

from decimal import Decimal
from datetime import timedelta


def duration(duration_string): #example: '5d3h2m1s'
    duration_string = duration_string.lower()
    total_seconds = Decimal('0')
    prev_num = []
    for character in duration_string:
        if character.isalpha():
            if prev_num:
                num = Decimal(''.join(prev_num))
                if character == 'd':
                    total_seconds += num * 60 * 60 * 24
                elif character == 'h':
                    total_seconds += num * 60 * 60
                elif character == 'm':
                    total_seconds += num * 60
                elif character == 's':
                    total_seconds += num
                prev_num = []
        elif character.isnumeric() or character == '.':
            prev_num.append(character)
    return timedelta(seconds=float(total_seconds))

Très bon exemple de


0 commentaires

0
votes

Vous pouvez utiliser une expression régulière pour extraire les parties d'unité nombre / temps, puis rechercher un multiplicateur dans un dictionnaire. De cette façon, il est un peu plus court et probablement beaucoup plus lisible que votre analyse manuelle et if / elif chain.

def duration(string):
    mult = {"s": 1, "m": 60, "h": 60*60, "d": 60*60*24}
    parts = re.findall(r"(\d+(?:\.\d)?)([smhd])", string)
    total_seconds = sum(float(x) * mult[m] for x, m in parts)
    return timedelta(seconds=total_seconds)

print(duration("2d 4h 13m 5.2s"))
# 2 days, 4:03:05.200000

En tant que fonction:

>>> mult = {"s": 1, "m": 60, "h": 60*60, "d": 60*60*24}
>>> s = "2d 4h 13m 5.2s"
>>> re.findall(r"(\d+(?:\.\d)?)([smhd])", s)
[('2', 'd'), ('4', 'h'), ('3', 'm'), ('5.2', 's')]
>>> sum(float(x) * mult[m] for x, m in _)
187385.2

Cela garantira également que la partie numérique est en fait un nombre valide ( et pas n'importe quelle séquence de chiffres et de points). En outre, cela lèvera une exception si des unités de temps autres que les unités de temps autorisées sont utilisées.

La fonction pourrait être encore optimisée en pré-compilant l'expression régulière avec re.compile en dehors de la fonction. Quand je l'ai testé avec % timeit d'IPython, le mien s'est avéré un peu plus rapide (2,1µs contre 2,8µs pour le vôtre, à la fois sans la création de timedelta et avec juste float au lieu de Decimal ). De plus, je considérerais cela comme plus lisible en ayant un style beaucoup plus déclaratif et moins impératif , mais c'est certainement une question de goût et de préférences.


6 commentaires

Comment pouvons-nous compiler un benchmark? Pour moi, le code que j'ai trouvé est plus lisible.


@ozlevka Vraiment? Eh bien, je suppose que cela dépend de votre aisance avec la compréhension de dict, l'expression du générateur et, bien sûr, les expressions régulières. En fin de compte, vous devriez prendre tout ce avec quoi vous pouvez travailler. Mais notez, par exemple, que votre code considérerait également ... 2.3..4.5 un nombre valide ... mais si vous savez d'où vient l'entrée, cela ne doit pas être un problème.


@ozlevka Depuis que vous avez demandé un benchmark: % timeit d'IPython montre que le mien est légèrement plus rapide (3,2µs contre 4,6µs), mais cela semble être en grande partie dû à votre utilisation de Decimal < / code>, ce qui n'est probablement pas nécessaire (vous reconvertissez de toute façon en float ). Sans Decimal , c'est 3,2 µs contre 3,5 µs.


Votre code s'exécute en moyenne en 0,001150 ms si je compile regex avant, puis exécute en moyenne environ 0,000900 ms. L'analyse directe s'exécute également en moyenne 0,000900 ms. Alors les performances sont les mêmes. Mais de toute façon je pense que ce code devrait être lisible par beaucoup de gens et ne pas prendre le temps de le comprendre.


@ozlevka Je suis tout à fait d'accord avec vous, la lisibilité est plus importante que la micro-optimisation des performances. Je considère simplement mon code comme plus lisible, car il est plus explicite à quoi les parties sont censées ressembler (en utilisant l'expression régulière au lieu d'être réparties sur de nombreuses lignes de boucle et if / elif) et comment elles sont additionnées. Mais encore une fois, vous devez choisir ce avec quoi vous pouvez travailler le mieux. J'essaie juste de suggérer une alternative.


Je suis d'accord avec vous sur le nombre de chaînes. Et la qualité du code. De plus, je peux admettre que votre code est plus élégant et court. Mais je dois expliquer ce que le code fait pour mes clients. Et votre solution rend cette tâche impossible :)



0
votes

Voici ma solution; J'ai utilisé la bibliothèque python datetime et c'est timedelta:

import datetime

intervals = {
    "w": datetime.timedelta(weeks=1),
    "d": datetime.timedelta(days=1),
    "h": datetime.timedelta(hours=1),
    "m": datetime.timedelta(minutes=1),
    "s": datetime.timedelta(seconds=1)
    }

def parse_live_length(string):
    time_interval_start_index = 0

    for char in string:
        if char.isnumeric():
            time_interval_start_index += 1
        else:
            return int(string[0:time_interval_start_index]), string[time_interval_start_index:]

    return False

# "2w" used as an example
live_length = "2w"
time_scalar, ll_interval = parse_live_length(live_length)

for interval in intervals:
    if interval == ll_interval:
        new_delta = time_scalar * intervals[interval]
        break 

# Example of how it could be used
current = datetime.datetime.now()
new_time = new_delta + current
print(new_time.day, new_time.month, new_time.year)


0 commentaires