How to Change the time zone in Python logging? - python-2.7

I would like to change the timestamp in the log file so that it reflects my current time zone so that i can debug errors at a faster rate,
is it possible that i can change the time zone in the log file ?
currently my config is:
logging.basicConfig(filename='audit.log',
filemode='w',
level=logging.INFO,
format='%(asctime)s %(message)s',
datefmt='%m/%d/%Y %I:%M:%S %p')

How to log the timezone
%Z from strftime format
Windows
>>> import logging
>>> logging.basicConfig(format="%(asctime)s %(message)s", datefmt="%m/%d/%Y %I:%M:%S %p %Z")
>>> logging.error('test')
11/03/2017 02:29:54 PM Mountain Daylight Time test
Linux
>>> import logging
>>> logging.basicConfig(format="%(asctime)s %(message)s", datefmt="%m/%d/%Y %I:%M:%S %p %Z")
>>> logging.error('test')
11/03/2017 02:30:50 PM MDT test
If the question is
How do I log in a different timezone than the local time on the server?
part of the answer is logging.Formatter.converter, however, you have to understand naive and aware datetime objects. Unless you want to write your own timezone module, I highly suggest the pytz library (pip install pytz). Python 3 includes a UTC and UTC offset timezone, but there's rules you'll have to implement for daylight saving or other offsets, so I would suggest the pytz library, even for python 3.
For example,
>>> import datetime
>>> utc_now = datetime.datetime.utcnow()
>>> utc_now.isoformat()
'2019-05-21T02:30:09.422638'
>>> utc_now.tzinfo
(None)
If I apply a timezone to this datetime object, the time won't change (or will issue a ValueError for < python 3.7ish).
>>> mst_now = utc_now.astimezone(pytz.timezone('America/Denver'))
>>> mst_now.isoformat()
'2019-05-21T02:30:09.422638-06:00'
>>> utc_now.isoformat()
'2019-05-21T02:30:09.422638'
However, if instead, I do
>>> import pytz
>>> utc_now = datetime.datetime.now(tz=pytz.timezone('UTC'))
>>> utc_now.tzinfo
<UTC>
now we can create a properly translated datetime object in whatever timezone we wish
>>> mst_now = utc_now.astimezone(pytz.timezone('America/Denver'))
>>> mst_now.isoformat()
'2019-05-20T20:31:44.913939-06:00'
Aha! Now to apply this to the logging module.
Epoch timestamp to string representation with timezone
The LogRecord.created attribute is set to the time when the LogRecord was created (as returned by time.time()), from the time module. This returns a timestamp (seconds since the epoch). You can do your own translation to a given timezone, but again, I suggest pytz, by overriding the converter.
import datetime
import logging
import pytz
class Formatter(logging.Formatter):
"""override logging.Formatter to use an aware datetime object"""
def converter(self, timestamp):
dt = datetime.datetime.fromtimestamp(timestamp)
tzinfo = pytz.timezone('America/Denver')
return tzinfo.localize(dt)
def formatTime(self, record, datefmt=None):
dt = self.converter(record.created)
if datefmt:
s = dt.strftime(datefmt)
else:
try:
s = dt.isoformat(timespec='milliseconds')
except TypeError:
s = dt.isoformat()
return s
Python 3.5, 2.7
>>> logger = logging.root
>>> handler = logging.StreamHandler()
>>> handler.setFormatter(Formatter("%(asctime)s %(message)s"))
>>> logger.addHandler(handler)
>>> logger.setLevel(logging.DEBUG)
>>> logger.debug('test')
2019-05-20T22:25:10.758782-06:00 test
Python 3.7
>>> logger = logging.root
>>> handler = logging.StreamHandler()
>>> handler.setFormatter(Formatter("%(asctime)s %(message)s"))
>>> logger.addHandler(handler)
>>> logger.setLevel(logging.DEBUG)
>>> logger.debug('test')
2019-05-20T22:29:21.678-06:00 test
Substitute America/Denver with America/Anchorage for the posix timezone as defined by pytz
>>> next(_ for _ in pytz.common_timezones if 'Alaska' in _)
'US/Alaska'
US/Alaska is deprecated
>>> [_ for _ in pytz.all_timezones if 'Anchorage' in _]
['America/Anchorage']
Local
If you got to this question and answers looking for how to log the local timezone, then instead of hardcoding the timezone, get tzlocal (pip install tzlocal) and replace
tzinfo = pytz.timezone('America/Denver')
with
tzinfo = tzlocal.get_localzone()
Now it will work on whatever server runs the script, with the timezone on the server.
Caveat when not logging UTC
I should add, depending on the application, logging in local time zones can create ambiguity or at least confusion twice a year, where 2 AM is skipped or 1 AM repeats, and possibly others.

#!/usr/bin/python
from datetime import datetime
from pytz import timezone
import logging
def timetz(*args):
return datetime.now(tz).timetuple()
tz = timezone('Asia/Shanghai') # UTC, Asia/Shanghai, Europe/Berlin
logging.Formatter.converter = timetz
logging.basicConfig(
format="%(asctime)s %(levelname)s: %(message)s",
level=logging.INFO,
datefmt="%Y-%m-%d %H:%M:%S",
)
logging.info('Timezone: ' + str(tz))
Using pytz to define a timezone relative to UTC.
Based on the example by: secsilm

#!/usr/bin/env python
from datetime import datetime
import logging
import time
from pytz import timezone, utc
def main():
logging.basicConfig(format="%(asctime)s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S")
logger = logging.getLogger(__name__)
logger.error("default")
logging.Formatter.converter = time.localtime
logger.error("localtime")
logging.Formatter.converter = time.gmtime
logger.error("gmtime")
def customTime(*args):
utc_dt = utc.localize(datetime.utcnow())
my_tz = timezone("US/Eastern")
converted = utc_dt.astimezone(my_tz)
return converted.timetuple()
logging.Formatter.converter = customTime
logger.error("customTime")
# to find the string code for your desired tz...
# print(pytz.all_timezones)
# print(pytz.common_timezones)
if __name__ == "__main__":
main()
Ostensibly the pytz package is the blessed way of converting time zones in Python. So we start with datetime, convert, then get the (immutable) time_tuple to match return type of the time methods
Setting the logging.Formatter.converter function is recommended by this answer: (Python logging: How to set time to GMT).
Find your favorite TZ code by uncommenting the end lines

just add this pythonic line to your code (using pytz and datetime):
from pytz import timezone
from datetime import datetime
import logging
logging.Formatter.converter = lambda *args: datetime.now(tz=timezone('tz string name')).timetuple()
# quoting Ryan J McCall: to find the string name for your desired timezone...
# print(pytz.all_timezones)
# or print(pytz.common_timezones)

An alternative solution if you want to use logging configuration function:
import pytz
import logging
import logging.config
from datetime import datetime
tz = pytz.timezone('Asia/Tokyo')
class TokyoFormatter(logging.Formatter):
converter = lambda *args: datetime.now(tz).timetuple()
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'Tokyo': {
'()': TokyoFormatter,
'format': '%(asctime)s %(levelname)s: %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S'
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'Tokyo'
},
},
'loggers': {
'foo': {
'handlers': ['console'],
'level': 'INFO'
},
}
}
logging.config.dictConfig(LOGGING)
logger = logging.getLogger('foo')
logger.info('Just a test.')
Define the logging formatter, e.g., "TokyoFormatter". It has an attibute "converter", finishing the job of converting the time zone.
For more details, please refer to Customizing handlers with dictConfig().

import logging, time
from datetime import datetime, timedelta
logger = logging.getLogger(__name__)
converter = lambda x, y: (datetime.utcnow() - timedelta(
hours=7 if time.localtime().tm_isdst else 6)
).timetuple()
logging.Formatter.converter = converter
Edited as Elias points out the original answer didn't check for DST.

If you know your utc offset, you can define a function to correct the time and then pass it to logging.Formatter.converter.
For example, you want to convert the time to UTC+8 timezone, then:
import logging
import datetime
def beijing(sec, what):
'''sec and what is unused.'''
beijing_time = datetime.datetime.now() + datetime.timedelta(hours=8)
return beijing_time.timetuple()
logging.Formatter.converter = beijing
logging.basicConfig(
format="%(asctime)s %(levelname)s: %(message)s",
level=logging.INFO,
datefmt="%Y-%m-%d %H:%M:%S",
)
Just change the hours in datetime.timedelta(hours=8) depending on your situation.
Reference: https://alanlee.fun/2019/01/06/how-to-change-logging-date-timezone/

Related

Why django timezone offset is different?

Django 3.2.10, Python 3.9
settings.py
TIME_ZONE = 'Europe/Moscow'
script.py
from django.utils import timezone
tzinfo = timezone.localtime().tzinfo # <class 'pytz.tzfile.Europe/Moscow'>
tz = timezone.get_current_timezone() # <class 'pytz.tzfile.Europe/Moscow'>
dtz = timezone.get_default_timezone() # <class 'pytz.tzfile.Europe/Moscow'>
datetime_object = timezone.now()
print(datetime_object) # 2022-03-29 03:34:42.244830+00:00
print(datetime_object.replace(tzinfo=tzinfo)) # 2022-03-29 03:34:42.244830+03:00
print(datetime_object.replace(tzinfo=tz)) # 2022-03-29 03:34:42.244830+02:30
print(datetime_object.replace(tzinfo=dtz)) # 2022-03-29 03:34:42.244830+02:30
+0230 is not +0300, and the correct offset for this time zone is +0300.
Using tzinfo when building timezone-aware datetimes is (from what I heard) a bad practice and can lead to bugs.
With
`TIME_ZONE = 'Europe/Moscow'
you should use make_aware:
from django.utils import timezone
from datetime import datetime, time
local_now = timezone.localtime()
time_on_the_clock = time(5, 2)
timezone_aware_time = timezone.make_aware(datetime.combine(local_now, time_on_the_clock))
print(timezone_aware_time)
or using pytz:
from datetime import datetime
from pytz import timezone
datetime_object = datetime.now()
moscow_time = timezone('Europe/Moscow').localize(datetime_object, is_dst=None)
print(moscow_time)

list_jobs function min_creation_time error

Below is my code in Django frame (python 2.7) to list the jobs in Bigquery. I want to filter to just the ones in last two weeks but the min_creation_time in the list_jobs() function does not work and errors out for some reason. Please suggest
from __future__ import unicode_literals
from django.shortcuts import render
import thd_gbq_tools as bq
# Create your views here.
from django.http import HttpResponse
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from oauth2client.client import GoogleCredentials
from google.cloud import bigquery
import uuid
import os
import logging
import time
import json
from datetime import datetime,timedelta
from django.template import loader
from django.shortcuts import render
import pandas as pd
from collections import OrderedDict
from datetime import date
def home(request):
credentials = GoogleCredentials.get_application_default()
# Construct the service object for interacting with the BigQuery API.
bq_conn = build('bigquery', 'v2', credentials=credentials)
job_query_dict = []
import warnings
warnings.filterwarnings("ignore")
###Create the big query client
client =bigquery.Client(project='analytics-supplychain-thd')
###List the jobs in the client
jobs = client.list_jobs(all_users= True) # API request
for job in jobs:
job_create_timestamp = datetime.strptime((str(job.created).replace('+','.')).split('.')[0],'%Y-%m-%d %H:%M:%S')
job_ended_timestamp = datetime.strptime((str(job.ended).replace('+','.')).split('.')[0],'%Y-%m-%d %H:%M:%S')
job_query_dict.append([job.job_id, job.user_email , job_create_timestamp,job_ended_timestamp, job.state])
Table1 = sorted(job_query_dict,key=lambda x: (x[2]), reverse=True)
return render(request, 'j2_response.html', {'Table1':Table1})
This is the code I am using to assign the parameter that indicates the last 10 minutes for min_creation_time:
from datetime import datetime,timedelta
from datetime import date
ten_mins_ago = datetime.utcnow() - timedelta(minutes=10)
When indicating ten_mins_ago = datetime.utcnow() - timedelta(minutes=10) you are specifying that you want the BigQuery jobs that have been run for the last 10 minutes.
You can try this code snippet to list the BigQuery jobs made in the last 2 weeks:
from google.cloud import bigquery
from datetime import datetime, timedelta
from pytz import timezone
client = bigquery.Client(project = '[YOUR_PROJECT]')
local_timezone = timezone('US/Eastern')
two_weeks_ago = datetime.utcnow() - timedelta(days = 14)
local_two_weeks = local_timezone.localize(two_weeks_ago)
for job in client.list_jobs(all_users = True, max_results = 10, min_creation_time = local_two_weeks):
print(job.job_id, job.user_email)
If this snippet works for you, you can integrate it into your code. Should you get any errors, please state them so we can look further into the issue.

exe with pytz-2018.3.dist not working. raises UnknownTimeZoneError

I'm having a problem when building an exe from my scripts that use the pytz lib. I'm constantly getting the error:
File "pytz\__init__.pyc", line 180, in timezone UnknownTimeZoneError: 'Europe\Ljubljana'
No matter how I build the exe I get this error. Running the script works. I tried all suggestions that were posted here and on other sites.
My setup.py file:
from distutils.core import setup
import os.path
try:
import py2exe
has_py2exe = True
except ImportError, e:
has_py2exe = False
myScript=__import__("myScript")
options = {'py2exe': {'packages': ['pytz']}}
options['py2exe'] = {'dist_dir': 'dist'}
setup(
name="myScript",
version=myScript.CONST_VERSION,
console=[{
'script': 'myScript.py',
'copyright': 'None',
'company_name': 'None'
}],
options=options,
)
if has_py2exe:
import zipfile
zipfile_path = os.path.join(options['py2exe']['dist_dir'], 'library.zip')
z = zipfile.ZipFile(zipfile_path, 'a')
import pytz
assert (pytz.__file__.endswith('__init__.pyc') or pytz.__file__.endswith('__init__.py')), pytz.__file__
zoneinfo_dir = os.path.join(os.path.dirname(pytz.__file__), 'zoneinfo')
disk_basedir = os.path.dirname(os.path.dirname(pytz.__file__))
for absdir, directories, filenames in os.walk(zoneinfo_dir):
assert absdir.startswith(disk_basedir), (absdir, disk_basedir)
zip_dir = absdir[len(disk_basedir):]
for f in filenames:
z.write(os.path.join(absdir, f), os.path.join(zip_dir, f))
z.close()
This builds the exe and includes the zoneinfo dir and all timezone files to library.zip.
I only use the pytz in one function where I convert the CEST timestamp to UTC timestamp to store on the server.
The function:
from datetime import datetime
import pytz
def date_time_utc(date_str):
date_tz = pytz.timezone("Europe/Ljubljana")
fmt = '%Y-%m-%d %H:%M:%S'
date_str_dt_object = datetime.strptime(date_str, fmt)
date_str_dt_object = date_tz.localize(date_str_dt_object)
date_utc = date_str_dt_object.astimezone(pytz.timezone('UTC'))
return date_utc.strftime(fmt)
The setup script for the pytz is from here. All other things like this script and any other suggested fixes raised the exactly same error.
Has any one figured out how to get this working?

RuntimeWarning: DateTimeField received a naive datetime

I m trying to send a simple mail using IPython. I have not set up any models still getting this error. What can be done?
Error :
/home/sourabh/Django/learn/local/lib/python2.7/site-packages/django/db/models/fields/init.py:827: RuntimeWarning: DateTimeField received a naive datetime (2013-09-04 14:14:13.698105) while time zone support is active.
RuntimeWarning)
Tried : The first step is to add USE_TZ = True to your settings file and install pytz (if possible).
Error changed:
(learn)sourabh#sL:~/Django/learn/event$ python manage.py shell
/home/sourabh/Django/learn/local/lib/python2.7/site-packages/django/db/backends/sqlite3/base.py:53: RuntimeWarning: SQLite received a naive datetime (2013-09-05 00:59:32.181872) while time zone support is active.
RuntimeWarning)
The problem is not in Django settings, but in the date passed to the model. Here's how a timezone-aware object looks like:
>>> from django.utils import timezone
>>> import pytz
>>> timezone.now()
datetime.datetime(2013, 11, 20, 20, 8, 7, 127325, tzinfo=pytz.UTC)
And here's a naive object:
>>> from datetime import datetime
>>> datetime.now()
datetime.datetime(2013, 11, 20, 20, 9, 26, 423063)
So if you are passing email date anywhere (and it eventually gets to some model), just use Django's now(). If not, then it's probably an issue with an existing package that fetches date without timezone and you can patch the package, ignore the warning or set USE_TZ to False.
Use django.utils.timezone.make_aware function to make your naive datetime objects timezone aware and avoid those warnings.
It converts naive datetime object (without timezone info) to the one that has timezone info (using timezone specified in your django settings if you don't specify it explicitly as a second argument):
import datetime
from django.conf import settings
from django.utils.timezone import make_aware
naive_datetime = datetime.datetime.now()
naive_datetime.tzinfo # None
settings.TIME_ZONE # 'UTC'
aware_datetime = make_aware(naive_datetime)
aware_datetime.tzinfo # <UTC>
Just to fix the error to set current time
from django.utils import timezone
import datetime
datetime.datetime.now(tz=timezone.utc) # you can use this value
Quick and dirty - Turn it off:
USE_TZ = False
in your settings.py
make sure settings.py has
USE_TZ = True
In your python file:
from django.utils import timezone
timezone.now() # use its value in model field
One can both fix the warning and use the timezone specified in settings.py, which might be different from UTC.
For example in my settings.py I have:
USE_TZ = True
TIME_ZONE = 'Europe/Paris'
Here is a solution; the advantage is that str(mydate) gives the correct time:
>>> from datetime import datetime
>>> from django.utils.timezone import get_current_timezone
>>> mydate = datetime.now(tz=get_current_timezone())
>>> mydate
datetime.datetime(2019, 3, 10, 11, 16, 9, 184106,
tzinfo=<DstTzInfo 'Europe/Paris' CET+1:00:00 STD>)
>>> str(mydate)
'2019-03-10 11:16:09.184106+01:00'
Another equivalent method is using make_aware, see dmrz post.
If you are trying to transform a naive datetime into a datetime with timezone in django, here is my solution:
>>> import datetime
>>> from django.utils import timezone
>>> t1 = datetime.datetime.strptime("2019-07-16 22:24:00", "%Y-%m-%d %H:%M:%S")
>>> t1
datetime.datetime(2019, 7, 16, 22, 24)
>>> current_tz = timezone.get_current_timezone()
>>> t2 = current_tz.localize(t1)
>>> t2
datetime.datetime(2019, 7, 16, 22, 24, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>)
>>>
t1 is a naive datetime and t2 is a datetime with timezone in django's settings.
You can also override settings, particularly useful in tests:
from django.test import override_settings
with override_settings(USE_TZ=False):
# Insert your code that causes the warning here
pass
This will prevent you from seeing the warning, at the same time anything in your code that requires a timezone aware datetime may give you problems. If this is the case, see kravietz answer.
In the model, do not pass the value:
timezone.now()
Rather, remove the parenthesis, and pass:
timezone.now
If you continue to get a runtime error warning, consider changing the model field from DateTimeField to DateField.
If you need to convert the actual date string to date object, I have got rid of the warning by simply using astimezone:
>>> from datetime import datetime, timezone
>>> datetime_str = '2013-09-04 14:14:13.698105'
>>> datetime_object = datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S.%f")
>>> datetime_object.astimezone(timezone.utc)
datetime.datetime(2013, 9, 4, 6, 14, 13, 698105, tzinfo=datetime.timezone.utc)
I encountered this warning when using the following model.
from datetime import datetime
class MyObject(models.Model):
my_date = models.DateTimeField(default=datetime.now)
To fix it, I switched to the following default.
from django.utils import timezone
class MyObject(models.Model):
my_date = models.DateTimeField(default=timezone.now)

Unix timestamp to datetime in django with timezone

I have a javascript calendar that is sending me a unixtimestamp. I am in Singapore. I want this timestamp to be interpreted as a Singapore timestamp and then converted to utc for comparisons with the db.
I cant, for the life of myself, figure out how to tell django that this time stamp is from the current timezone, Singapore.
When i do a print statement of the timestamp, it adds 8 hours to the time (which means that django thinks I input the time in utc and is localizing it to the Singaporean context)
Among many other things, I tried:
start=datetime.datetime.fromtimestamp(int(start_date)).replace(tzinfo=get_current_timezone())
The start_date is 1325376000 (which translates to 2012-01-01 00:00:00)
However,when i print the output of this I get 2012-01-01 08:00:00+06:55. I dont even know where +06:55 is coming from when singapore is +08:00. I am SO lost.
Thanks for your help.
settings.py:
TIME_ZONE = 'Asia/Singapore'
USE_TZ = True
all methods above are valide, but not "django like".
Here is a simple example, how a django programmer would do that:
from datetime import datetime
from django.utils.timezone import make_aware
# valid timestamp
value = 1531489250
# you can pass the following obj to a DateTimeField, when your settings.USE_TZ == True
datetime_obj_with_tz = make_aware(datetime.fromtimestamp(value))
See more utilites on the Django github timezone module to get whole overview...
Assuming you've got pytz installed:
from datetime import datetime
import pytz
local_tz = pytz.timezone("Asia/Singapore")
utc_dt = datetime.utcfromtimestamp(timestamp).replace(tzinfo=pytz.utc)
local_dt = local_tz.normalize(utc_dt.astimezone(local_tz))
For example:
>>> from datetime import datetime
>>> import pytz
>>> local_tz = pytz.timezone("Asia/Singapore")
>>> utc_dt = datetime.utcfromtimestamp(1325376000).replace(tzinfo=pytz.utc)
>>> utc_dt
datetime.datetime(2012, 1, 1, 0, 0, tzinfo=<UTC>)
>>> local_dt = local_tz.normalize(utc_dt.astimezone(local_tz))
>>> local_dt
datetime.datetime(2012, 1, 1, 8, 0, tzinfo=<DstTzInfo 'Asia/Singapore' SGT+8:00:00 STD>)
>>> local_dt.replace(tzinfo=None)
datetime.datetime(2012, 1, 1, 8, 0)
Pass the pytz tzinfo object to fromtimestamp() method:
#!/usr/bin/env python
from datetime import datetime
import pytz # $ pip install pytz
tz = pytz.timezone("Asia/Singapore")
print(datetime.fromtimestamp(1325376000, tz))
# -> 2012-01-01 08:00:00+08:00
Note: the result object is timezone-aware: you could compare it with other aware datetime objects i.e., you don't need to convert it to UTC for comparison -- you can use it as is.
I dont even know where +06:55 is coming from when singapore is +08:00.
You see +06:55 due to the invalid .replace() call. get_current_timezone() returns pytz.timezone("Asia/Singapore") that has a variable utc offset (it may have a different utc offset at different dates). When you call .replace() some random (depends on the implementation) tzinfo object is used. The issue is that .replace() method does not allow pytz.timezone("Asia/Singapore") to choose the correct tzinfo for the input date.
>>> list(tz._tzinfos.values())
[<DstTzInfo 'Asia/Singapore' MALT+7:00:00 STD>,
<DstTzInfo 'Asia/Singapore' MALT+7:20:00 STD>,
<DstTzInfo 'Asia/Singapore' JST+9:00:00 STD>,
<DstTzInfo 'Asia/Singapore' SMT+6:55:00 STD>,
<DstTzInfo 'Asia/Singapore' SGT+7:30:00 STD>,
<DstTzInfo 'Asia/Singapore' MALT+7:30:00 STD>,
<DstTzInfo 'Asia/Singapore' MALST+7:20:00 DST>,
<DstTzInfo 'Asia/Singapore' LMT+6:55:00 STD>,
<DstTzInfo 'Asia/Singapore' SGT+8:00:00 STD>]
i.e., both +06:55 and +0800 are valid (at different dates) for Singapore. That is why you should use .replace() only with timezones that have a constant utc offset such as the utc timezone itself (the offset is zero, always for any date).
fromtimestamp(,tz) method calls tz.fromutc() internally that allows tz to choose the correct offset for a given utc time.