795

What I need to do

I have a timezone-unaware datetime object, to which I need to add a time zone in order to be able to compare it with other timezone-aware datetime objects. I do not want to convert my entire application to timezone unaware for this one legacy case.

What I've Tried

First, to demonstrate the problem:

Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime
>>> import pytz
>>> unaware = datetime.datetime(2011,8,15,8,15,12,0)
>>> unaware
datetime.datetime(2011, 8, 15, 8, 15, 12)
>>> aware = datetime.datetime(2011,8,15,8,15,12,0,pytz.UTC)
>>> aware
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> aware == unaware
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes

First, I tried astimezone:

>>> unaware.astimezone(pytz.UTC)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: astimezone() cannot be applied to a naive datetime
>>>

It's not terribly surprising this failed, since it's actually trying to do a conversion. Replace seemed like a better choice (as per How do I get a value of datetime.today() in Python that is "timezone aware"?):

>>> unaware.replace(tzinfo=pytz.UTC)
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> unaware == aware
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes
>>> 

But as you can see, replace seems to set the tzinfo, but not make the object aware. I'm getting ready to fall back to doctoring the input string to have a timezone before parsing it (I'm using dateutil for parsing, if that matters), but that seems incredibly kludgy.

Also, I've tried this in both Python 2.6 and Python 2.7, with the same results.

Context

I am writing a parser for some data files. There is an old format I need to support where the date string does not have a timezone indicator. I've already fixed the data source, but I still need to support the legacy data format. A one time conversion of the legacy data is not an option for various business BS reasons. While in general, I do not like the idea of hard-coding a default timezone, in this case it seems like the best option. I know with reasonable confidence that all the legacy data in question is in UTC, so I'm prepared to accept the risk of defaulting to that in this case.

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Mark Tozzi
  • 10,353
  • 5
  • 22
  • 30
  • 2
    `unaware.replace()` would return `None` if it were modifying `unaware` object inplace. The REPL shows that `.replace()` returns a new `datetime` object here. – jfs Aug 15 '11 at 16:40
  • 3
    What I needed when I came here: `import datetime; datetime.datetime.now(datetime.timezone.utc)` – Martin Thoma Jan 16 '18 at 09:32
  • 4
    @MartinThoma I would use the named `tz` arg to be more readable: `datetime.datetime.now(tz=datetime.timezone.utc)` – Asclepius Oct 14 '19 at 02:45
  • 1
    `astimezone()` can now (starting with 3.6) be called on a naive object, and its parameter can (starting with 3.3) be omitted, so the solution is as simple as `unaware.astimezone()` – vashekcz Feb 25 '21 at 21:49
  • 3
    I found the trick : Europe/Paris, Berlin, CET, ... are completety bugged in pytz and I was in a mess of instability for months because of that. I replaced all that by dateutil.tz.gettz(...) and now my code is stable and works ! My advice : abandon completely pytz ! – herve-guerin Sep 29 '22 at 10:35

16 Answers16

850

In general, to make a naive datetime timezone-aware, use the localize method:

import datetime
import pytz

unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
aware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0, pytz.UTC)

now_aware = pytz.utc.localize(unaware)
assert aware == now_aware

For the UTC timezone, it is not really necessary to use localize since there is no daylight savings time calculation to handle:

now_aware = unaware.replace(tzinfo=pytz.UTC)

works. (.replace returns a new datetime; it does not modify unaware.)

Catskul
  • 17,916
  • 15
  • 84
  • 113
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • 14
    Well, I feel silly. Replace returns a new datetime. It says that right there in the docs too, and I completely missed that. Thanks, that's exactly what I was looking for. – Mark Tozzi Aug 15 '11 at 14:24
  • 2
    "Replace returns a new datetime." Yep. The hint that the REPL gives you is that it's showing you the returned value. :) – Karl Knechtel Aug 15 '11 at 14:33
  • thanks, I had use from dt_aware to unware dt_unware = datetime.datetime(*(dt_aware.timetuple()[:6])), – Sérgio Nov 22 '11 at 00:24
  • 5
    if the timezone is not UTC then don't use the constructor directly: `aware = datetime(..., tz)`, use `.localize()` instead. – jfs Mar 08 '14 at 21:07
  • 2
    It is worth mentioning that local time may be ambiguous. `tz.localize(..., is_dst=None)` asserts that it is not . – jfs Mar 08 '14 at 21:10
  • looking at pytz source, `localize` simply calls `.replace(tzinfo= ... )` provided you called it on an unaware `datetime`; anything else throws an exception. I don't see anything about it doing calculations. – Tim Tisdall Dec 21 '15 at 18:59
  • ah... I found it. :) It does that for `pytz.utc.localize`, but some of the other timezones do calculations. – Tim Tisdall Dec 21 '15 at 19:03
  • @TimTisdall: Right. For timezones which are subclasses of `DstTzInfo`, `localize` is defined [like this](http://bazaar.launchpad.net/~stub/pytz/devel/view/head:/src/pytz/tzinfo.py#L244). – unutbu Dec 21 '15 at 19:06
  • A simple one-liner to copy the timezone info from the aware to the unaware object - independently of the timezone: ```unaware = aware.tzinfo.localize(unaware)```. This implies however that the aware object has pytz timezone object. – iurii Apr 06 '17 at 13:45
  • This didn't actually work for me. I found that it's actually just setting the timezone to UTC -- which is wrong if you are not on UTC time. – gloriphobia Jun 27 '17 at 14:29
  • What is the difference between using [`replace()`](https://docs.python.org/3/library/datetime.html#datetime.date.replace) and just setting `unaware.tzinfo = datetime.timezone.utc`? – Jens Jul 22 '17 at 21:45
  • @Jens: There isn't much difference, except that one is a function call and the other is an assignment. – unutbu Jul 22 '17 at 22:46
  • 13
    putz has a bug that sets Amsterdam timezone to + 20 minutes to UTC, some archaic timezone from 1937. You had one job pytz. – Boris Feb 18 '20 at 16:09
  • ValueError: Not naive datetime (tzinfo is already set) – CS QGB Jun 20 '22 at 03:34
  • 1
    I found the trick : Europe/Paris, Berlin, CET, ... are completety bugged in pytz and I was in a mess of instability for months because of that. I replaced all that by dateutil.tz.gettz(...) and now my code is stable and works ! My advice : abandon completely pytz ! – herve-guerin Sep 29 '22 at 10:36
386

All of these examples use an external module, but you can achieve the same result using just the datetime module, as also presented in this SO answer:

from datetime import datetime, timezone

dt = datetime.now()
dt = dt.replace(tzinfo=timezone.utc)

print(dt.isoformat())
# '2017-01-12T22:11:31+00:00'

Fewer dependencies and no pytz issues.

NOTE: If you wish to use this with python3 and python2, you can use this as well for the timezone import (hardcoded for UTC):

try:
    from datetime import timezone
    utc = timezone.utc
except ImportError:
    #Hi there python2 user
    class UTC(tzinfo):
        def utcoffset(self, dt):
            return timedelta(0)
        def tzname(self, dt):
            return "UTC"
        def dst(self, dt):
            return timedelta(0)
    utc = UTC()
Zoe
  • 27,060
  • 21
  • 118
  • 148
kang
  • 3,869
  • 1
  • 8
  • 3
  • 25
    Very good answer for preventing the `pytz` issues, I'm glad I scrolled down a bit! Didn't want to tackle with `pytz` on my remote servers indeed :) – Tregoreg Feb 01 '17 at 20:20
  • 12
    Note that `from datetime import timezone` works in py3 but not py2.7. – 7yl4r Feb 20 '17 at 16:44
  • 21
    You should note that `dt.replace(tzinfo=timezone.utc)` returns a new datetime, it does not modify `dt` in place. (I will edit to show this). – Blairg23 Mar 28 '17 at 00:58
  • 4
    How might you, instead of using timezone.utc, provide a different timezone as a string (eg "America/Chicago")? – bumpkin Sep 26 '17 at 18:36
  • @rapt For python2 you may miss the `from datetime import tzinfo` when defining `utc` in the except path. – zk82 Jan 03 '19 at 11:06
  • i think this answer is wrong: timezones change over time! eg. "Europe/Vienna" once hat an offset of "01:05", so setting it with `replace()` would offset the datetime by that, and not the current offset of "01:00". resulting in weirdness like `>>> tz.normalize(datetime.strptime('2019-02-12', '%Y-%m-%d').replace(tzinfo=tz)).isoformat() '2019-02-12T23:55:00+01:00'` better use localize: `>>> tz.localize(datetime.datetime.strptime('2019-02-12', '%Y-%m-%d')).isoformat() '2019-02-12T00:00:00+01:00'` – Florian Feb 13 '19 at 11:38
  • 6
    @bumpkin better late than never, i guess: `tz = pytz.timezone('America/Chicago')` – Florian Feb 13 '19 at 11:40
  • @bumpkin I couldn't find a way to do it without using pytz. – JoeyC Aug 15 '19 at 21:07
  • 1
    You can save a line and do `datetime.now(timezone.utc)` , as in ufficial doc https://docs.python.org/3/library/datetime.html#datetime.datetime.utcnow – Christian Pao. Mar 03 '21 at 14:02
  • +1 for pure Python solution. BTW, another way of doing it via a one-liner is `datetime.datetime.combine(datetime.datetime(2022, 4, 26), datetime.datetime.min.time(), datetime.timezone.utc)` – akki Apr 26 '22 at 15:50
116

I wrote this Python 2 script in 2011, but never checked if it works on Python 3.

I had moved from dt_aware to dt_unaware:

dt_unaware = dt_aware.replace(tzinfo=None)

and dt_unware to dt_aware:

from pytz import timezone
localtz = timezone('Europe/Lisbon')
dt_aware = localtz.localize(dt_unware)
Wouter
  • 534
  • 3
  • 14
  • 22
Sérgio
  • 6,966
  • 1
  • 48
  • 53
  • 4
    you could use `localtz.localize(dt_unware, is_dst=None)` to raise an exception if `dt_unware` represents non-existing or ambiguous local time (note: there were no such issue in the previous revision of your answer where `localtz` was UTC because UTC has no DST transitions – jfs May 15 '14 at 19:55
  • @J.F. Sebastian , first comment applied – Sérgio May 15 '14 at 20:59
  • 4
    I appreciate you showing both directions of the conversion. – Christian Long Jun 24 '16 at 01:08
  • @Sérgio and when you put that argument in .replace and get "TypeError: replace() got an unexpected keyword argument 'tzinfo'"? What can be done for this problem? – Gustavo Rottgering Jun 22 '20 at 19:36
52

I use this statement in Django to convert an unaware time to an aware:

from django.utils import timezone

dt_aware = timezone.make_aware(dt_unaware, timezone.get_current_timezone())
Community
  • 1
  • 1
Googol
  • 2,815
  • 2
  • 22
  • 13
  • 4
    I do like this solution (+1), but it is dependent on Django, which is not what they were looking for (-1). =) – mkoistinen Aug 28 '15 at 17:54
  • 6
    You don't actually the second argument. The [default argument](https://docs.djangoproject.com/en/1.10/ref/utils/#django.utils.timezone.make_aware) (None) will mean the local timezone is used implicitly. Same with the DST (which is the third argument_ – Oli Sep 12 '16 at 11:24
  • If you want to convert to UTC: `dt_aware = timezone.make_aware(dt_unaware, timezone.utc)` – Eduardo Tolmasquim Aug 12 '21 at 19:15
52

Python 3.9 adds the zoneinfo module so now only the standard library is needed!

from zoneinfo import ZoneInfo
from datetime import datetime
unaware = datetime(2020, 10, 31, 12)

Attach a timezone:

>>> unaware.replace(tzinfo=ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 12:00:00+09:00'

Attach the system's local timezone:

>>> unaware.replace(tzinfo=ZoneInfo('localtime'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='localtime'))
>>> str(_)
'2020-10-31 12:00:00+01:00'

Subsequently it is properly converted to other timezones:

>>> unaware.replace(tzinfo=ZoneInfo('localtime')).astimezone(ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 20, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 20:00:00+09:00'

Wikipedia list of available time zones


Windows has no system time zone database, so here an extra package is needed:

pip install tzdata  

There is a backport to allow use of zoneinfo in Python 3.6 to 3.8:

pip install backports.zoneinfo

Then:

from backports.zoneinfo import ZoneInfo
HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
xjcl
  • 12,848
  • 6
  • 67
  • 89
  • 4
    on Windows, you also need to `pip install tzdata` – FObersteiner Jun 11 '20 at 17:19
  • @MrFuppes Thanks for the tip! I'll test this tomorrow and it to my answer. Do you know what the situation is on Macs? – xjcl Jun 11 '20 at 22:32
  • @xjcl You need `pip install tzdata` on any platform where the operating system doesn't provide a time zone database. Macs should work out of the box. It will not hurt to install `tzdata` unconditionally (since the system data is prioritized over `tzdata`) if your application needs time zone data. – Paul Sep 21 '20 at 19:29
  • @Paul That's unfortunate since I was really hoping for a standard library solution – xjcl Sep 26 '20 at 13:48
  • 1
    @xjcl tzdata is effectively part of the standard library (we call it a "first party package"), you just need to pip install it because it has a much faster release cadence than CPython. – Paul Sep 27 '20 at 00:18
  • 1
    I wanted to mention, it's very easy to add this to requirements.txt conditionally for Windows only: `tzdata; sys_platform == "win32"` (from: https://stackoverflow.com/a/35614580/705296) – Joe Sadoski May 31 '22 at 14:37
27

I agree with the previous answers, and is fine if you are ok to start in UTC. But I think it is also a common scenario for people to work with a tz aware value that has a datetime that has a non UTC local timezone.

If you were to just go by name, one would probably infer replace() will be applicable and produce the right datetime aware object. This is not the case.

the replace( tzinfo=... ) seems to be random in its behaviour. It is therefore useless. Do not use this!

localize is the correct function to use. Example:

localdatetime_aware = tz.localize(datetime_nonaware)

Or a more complete example:

import pytz
from datetime import datetime
pytz.timezone('Australia/Melbourne').localize(datetime.now())

gives me a timezone aware datetime value of the current local time:

datetime.datetime(2017, 11, 3, 7, 44, 51, 908574, tzinfo=<DstTzInfo 'Australia/Melbourne' AEDT+11:00:00 DST>)
paolov
  • 2,139
  • 1
  • 34
  • 43
  • 8
    This needs more upvotes, trying to do `replace(tzinfo=...)` on a timezone other than UTC will foul up your datetime. I got `-07:53` instead of `-08:00` for instance. See https://stackoverflow.com/a/13994611/1224827 – Blairg23 Jul 27 '17 at 00:09
  • 1
    Can you give a reproducible example of `replace(tzinfo=...)` having unexpected behavior? – xjcl Jun 01 '20 at 23:13
  • Thanks a lot. I wasted a lot of time trying to use `replace()` but it didn't work. – FrackeR011 Nov 12 '21 at 10:47
  • Can anyone explaim `` to me? What does it mean? – Sorry for my bad English Sep 05 '22 at 03:53
  • 1
    @SorryformybadEnglish i think that's the `repr()` of a timezone object, giving different representations. the text name (`'Australia/Melbourne'`), the code (`AEDT` - Australian Eastern Daylight Time), the offset from UTC (`+11:00:00`), and i guess if daylight saving time is enabled (`DST`) – starwarswii Apr 11 '23 at 21:20
13

Use dateutil.tz.tzlocal() to get the timezone in your usage of datetime.datetime.now() and datetime.datetime.astimezone():

from datetime import datetime
from dateutil import tz

unlocalisedDatetime = datetime.now()

localisedDatetime1 = datetime.now(tz = tz.tzlocal())
localisedDatetime2 = datetime(2017, 6, 24, 12, 24, 36, tz.tzlocal())
localisedDatetime3 = unlocalisedDatetime.astimezone(tz = tz.tzlocal())
localisedDatetime4 = unlocalisedDatetime.replace(tzinfo = tz.tzlocal())

Note that datetime.astimezone will first convert your datetime object to UTC then into the timezone, which is the same as calling datetime.replace with the original timezone information being None.

Daniel
  • 8,655
  • 5
  • 60
  • 87
Ahmet
  • 1,395
  • 1
  • 10
  • 14
10

This codifies @Sérgio and @unutbu's answers. It will "just work" with either a pytz.timezone object or an IANA Time Zone string.

def make_tz_aware(dt, tz='UTC', is_dst=None):
    """Add timezone information to a datetime object, only if it is naive."""
    tz = dt.tzinfo or tz
    try:
        tz = pytz.timezone(tz)
    except AttributeError:
        pass
    return tz.localize(dt, is_dst=is_dst) 

This seems like what datetime.localize() (or .inform() or .awarify()) should do, accept both strings and timezone objects for the tz argument and default to UTC if no time zone is specified.

Community
  • 1
  • 1
hobs
  • 18,473
  • 10
  • 83
  • 106
  • 1
    Thanks, this helped me "brand" a raw datetime object as "UTC" without the system first assuming it to be local time and then recalculating the values! – Nikhil VJ Nov 03 '19 at 12:34
9

for those that just want to make a timezone aware datetime

import datetime

datetime.datetime(2019, 12, 7, tzinfo=datetime.timezone.utc)

for those that want a datetime with a non utc timezone starting in python 3.9 stdlib

import datetime
from zoneinfo import ZoneInfo

datetime.datetime(2019, 12, 7, tzinfo=ZoneInfo("America/Los_Angeles")) 
Harry Moreno
  • 10,231
  • 7
  • 64
  • 116
  • How is this different to the main answer? – Jack Jul 28 '20 at 10:56
  • I don't care to use the localize function. This answer is more succinct for those trying to solve their problem quickly (what I wish was the accepted answer). – Harry Moreno Jul 28 '20 at 18:12
  • The localize function is just there to test the assert method right? It's not actually required? `aware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0, pytz.UTC)` is the same as you've written they have just named the parameter no? – Jack Jul 29 '20 at 08:39
  • the assert is there to demonstrate how to change a datetime between timezone aware and unaware. _I_ actually provided the keyword argument for clarity. you can omit the keyword and rely on positional arguments if you prefer. kwargs are less error prone though. – Harry Moreno Jul 29 '20 at 16:46
  • So the only difference is the using the named parameter? – Jack Jul 30 '20 at 16:00
  • and the assert and the extra paragraphs. Again, the point of my answer is for a quick solution if someone reaches this question looking to make a timezone aware datetime. – Harry Moreno Jul 30 '20 at 22:36
  • 1
    tzinfo named parameter is not mentioned in the accepted answer. – Aaron Sep 09 '20 at 14:20
  • you don't even need `pytz` for this; use `datetime.timezone.utc` instead. – FObersteiner Jul 29 '21 at 10:10
  • What if you want a timezone other than UTC? – étale-cohomology Sep 24 '21 at 17:09
  • with pytz, ``` from pytz import timezone eastern = timezone('US/Eastern') datetime.datetime(2019, 12, 7, tzinfo=eastern) ``` – Harry Moreno Sep 30 '21 at 14:05
4

Yet another way of having a datetime object NOT naive:

>>> from datetime import datetime, timezone
>>> datetime.now(timezone.utc)
datetime.datetime(2021, 5, 1, 22, 51, 16, 219942, tzinfo=datetime.timezone.utc)
ivanleoncz
  • 9,070
  • 7
  • 57
  • 49
2

Changing between timezones

import pytz
from datetime import datetime

other_tz = pytz.timezone('Europe/Madrid')

# From random aware datetime...
aware_datetime = datetime.utcnow().astimezone(other_tz)
>> 2020-05-21 08:28:26.984948+02:00

# 1. Change aware datetime to UTC and remove tzinfo to obtain an unaware datetime
unaware_datetime = aware_datetime.astimezone(pytz.UTC).replace(tzinfo=None)
>> 2020-05-21 06:28:26.984948

# 2. Set tzinfo to UTC directly on an unaware datetime to obtain an utc aware datetime
aware_datetime_utc = unaware_datetime.replace(tzinfo=pytz.UTC)
>> 2020-05-21 06:28:26.984948+00:00

# 3. Convert the aware utc datetime into another timezone
reconverted_aware_datetime = aware_datetime_utc.astimezone(other_tz)
>> 2020-05-21 08:28:26.984948+02:00

# Initial Aware Datetime and Reconverted Aware Datetime are equal
print(aware_datetime1 == aware_datetime2)
>> True
borchvm
  • 3,533
  • 16
  • 44
  • 45
Shide
  • 71
  • 4
1

quite new to Python and I encountered the same issue. I find this solution quite simple and for me it works fine (Python 3.6):

unaware=parser.parse("2020-05-01 0:00:00")
aware=unaware.replace(tzinfo=tz.tzlocal()).astimezone(tz.tzlocal())
ilmatte
  • 51
  • 3
1

Here is a simple solution to minimize changes to your code:

from datetime import datetime
import pytz

start_utc = datetime.utcnow()
print ("Time (UTC): %s" % start_utc.strftime("%d-%m-%Y %H:%M:%S"))

Time (UTC): 09-01-2021 03:49:03

tz = pytz.timezone('Africa/Cairo')
start_tz = datetime.now().astimezone(tz)
print ("Time (RSA): %s" % start_tz.strftime("%d-%m-%Y %H:%M:%S"))

Time (RSA): 09-01-2021 05:49:03

leenremm
  • 1,083
  • 13
  • 19
0

In the format of unutbu's answer; I made a utility module that handles things like this, with more intuitive syntax. Can be installed with pip.

import datetime
import saturn

unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
now_aware = saturn.fix_naive(unaware)

now_aware_madrid = saturn.fix_naive(unaware, 'Europe/Madrid')
Turtles Are Cute
  • 3,200
  • 6
  • 30
  • 38
0
  • As per the documentation datetime.utcnow:

    • Warning: Because naive datetime objects are treated by many datetime methods as local times, it is preferred to use aware datetimes to represent times in UTC. As such, the recommended way to create an object representing the current time in UTC is by calling datetime.now(timezone.utc).

    • This option is covered in other answers, but the document citation is not.
  • As per the documentation datetime.utcfromtimestamp

  • Tested in python 3.11.2

from datetime import datetime, timezone
import time  # for timestamp
import pytz  # for aware comparison

now = datetime.now(tz=timezone.utc)
aware = datetime(now.year, now.month, now.day, now.hour, now.minute, now.second, now.microsecond, tzinfo=pytz.UTC)

print(now == aware)
[out]: True

fts = datetime.fromtimestamp(time.time(), tz=timezone.utc)
aware = datetime(fts.year, fts.month, fts.day, fts.hour, fts.minute, fts.second, fts.microsecond, tzinfo=pytz.UTC)

print(fts == aware)
[out]: True
Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
-3

Above all mentioned approaches, when it is a Unix timestamp, there is a very simple solution using pandas.

import pandas as pd

unix_timestamp = 1513393355
pst_tz = pd.Timestamp(unix_timestamp, unit='s', tz='US/Pacific')
utc_tz = pd.Timestamp(unix_timestamp, unit='s', tz='UTC')
blackraven
  • 5,284
  • 7
  • 19
  • 45