datetime: redondear / recortar el número de dígitos en microsegundos

Actualmente estoy registrando cosas y estoy usando mi propio formateador con un formatTime() personalizado formatTime() :

 def formatTime(self, _record, _datefmt): t = datetime.datetime.now() return t.strftime('%Y-%m-%d %H:%M:%S.%f') 

Mi problema es que los microsegundos, %f , son seis dígitos. ¿Hay alguna forma de escupir menos dígitos, como los tres primeros dígitos de los microsegundos?

La forma más sencilla sería utilizar el corte para cortar los tres últimos dígitos de los microsegundos:

 def format_time(): t = datetime.datetime.now() s = t.strftime('%Y-%m-%d %H:%M:%S.%f') return s[:-3] 

Si realmente quieres redondear el número en lugar de solo cortar, es un poco más de trabajo pero no es horrible:

 def format_time(): t = datetime.datetime.now() s = t.strftime('%Y-%m-%d %H:%M:%S.%f') tail = s[-7:] f = round(float(tail), 3) temp = "%.3f" % f return "%s%s" % (s[:-7], temp[1:]) 

Este método siempre debe devolver una marca de tiempo que se vea exactamente como esta (con o sin la zona horaria dependiendo de si el objeto dt entrada contiene uno):

2016-08-05T18: 18: 54.776 + 0000

Toma un objeto datetime como entrada (que puede producir con datetime.datetime.now() ). Para obtener la zona horaria como en la salida de mi ejemplo, deberá import pytz y pasar datetime.datetime.now(pytz.utc) .

 import pytz, datetime time_format(datetime.datetime.now(pytz.utc)) def time_format(dt): return "%s:%.3f%s" % ( dt.strftime('%Y-%m-%dT%H:%M'), float("%.3f" % (dt.second + dt.microsecond / 1e6)), dt.strftime('%z') ) 

Noté que algunos de los otros métodos anteriores omitirían el cero final si hubiera uno (por ejemplo, 0.870 convirtió en 0.87 ) y esto estaba causando problemas para el analizador en el que estaba introduciendo estas marcas de tiempo. Este método no tiene ese problema.

Edición: Como algunos lo señalan en los comentarios a continuación, el redondeo de esta solución (y la anterior) presenta problemas cuando el valor de microsegundos alcanza 999500, ya que 999.5 se redondea a 1000 (desbordamiento).

Si no es necesario volver a implementar Strftime para que sea compatible con el formato que queremos (el potencial desbordamiento causado por el redondeo debería propagarse hasta segundos, luego minutos, etc.), es mucho más simple truncar hasta los primeros 3 dígitos como se indica en el respuesta aceptada, o usando algo como:

'{:03}'.format(int(999999/1000))

– Respuesta original conservada a continuación –

En mi caso, estaba tratando de formatear una marca de datos con milisegundos formateados como ‘ddd’. La solución que terminé usando para obtener milisegundos fue usar el atributo de microsecond del objeto datetime , dividirlo por 1000.0, rellenarlo con ceros si es necesario y redondearlo con formato. Se parece a esto:

 '{:03.0f}'.format(datetime.now().microsecond / 1000.0) # Produces: '033', '499', etc. 

Este método permite una precisión flexible y consumirá todo el valor de microsegundos si especifica una precisión demasiado grande.

 def formatTime(self, _record, _datefmt, precision=3): dt = datetime.datetime.now() us = str(dt.microsecond) f = us[:precision] if len(us) > precision else us return "%d-%d-%d %d:%d:%d.%d" % (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, int(f)) 

Este método implementa el redondeo a 3 lugares decimales:

 import datetime from decimal import * def formatTime(self, _record, _datefmt, precision='0.001'): dt = datetime.datetime.now() seconds = float("%d.%d" % (dt.second, dt.microsecond)) return "%d-%d-%d %d:%d:%s" % (dt.year, dt.month, dt.day, dt.hour, dt.minute, float(Decimal(seconds).quantize(Decimal(precision), rounding=ROUND_HALF_UP))) 

strftime usar el método strftime propósito porque preferiría no modificar un objeto de fecha y hora completamente serializado sin volverlo a validar. De esta forma también se muestran los datos internos de la fecha en caso de que desee modificarla más.

En el ejemplo de redondeo, tenga en cuenta que la precisión se basa en cadenas para el módulo Decimal .

Aquí está mi solución usando regexp:

 import re # Capture 6 digits after dot in a group. regexp = re.compile(r'\.(\d{6})') def to_splunk_iso(dt): """Converts the datetime object to Splunk isoformat string.""" # 6-digits string. microseconds = regexp.search(dt.isoformat()).group(1) return regexp.sub('.%d' % round(float(microseconds) / 1000), dt.isoformat()) 

Una solución fácil que debería funcionar en todos los casos:

 def format_time(): t = datetime.datetime.now() if t.microsecond % 1000 >= 500: # check if there will be rounding up t = t + datetime.timedelta(milliseconds=1) # manually round up return t.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] 

Básicamente, primero redondeas manualmente el objeto de fecha, luego puedes recortar los microsegundos de forma segura.

Arreglando la solución propuesta en base a los comentarios de Pablojim :

 from datetime import datetime dt = datetime.now() dt_round_microsec = round(dt.microsecond/1000) #number of zeroes to round dt = dt.replace(microsecond=dt_round_microsec)