using python `eval()` with a method - python-3.x

My ultimate goal is to get the day, month and year of two days, today and yesterday in a nested for loop.
Below I am importing stuff and defining today and yesterday
import datetime as dt
from datetime import timedelta
today = dt.date.today()
yesterday = today - timedelta(1)
Now I am defining a dictionary to map the format of each year, month and day
d_day_format = {'year': '%y',
'month': '%m',
'day': '%d'}
So now I want to create the following variables: today_year (20), today_month (12), today_day (31), yesterday_year (20), yesterday_month (12) and yesterday_day (30)
I am using eval() to get the values of the variables from strings, as shown in the loop below
for d in 'today yesterday'.split():
for k in d_day_format.keys():
globals()[f"{d}_{k}"] = eval(d).eval(k).format(f"{d_day_format[k]}")
the first part of the variable assignment eval(d) works, though it appears I cannot use eval() for methods:
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
~/installed/python/demuxDelayer/demuxDelayer_v2.py in <module>
1 for d in 'today yesterday'.split():
2 for k in d_day_format.keys():
----> 3 globals()[f"{d}_{k}"] = eval(d).eval(k).format(f"{d_day_format[k]}")
AttributeError: 'datetime.date' object has no attribute 'eval'
Does anyone know how to fix this?

Python’s eval() allows you to evaluate arbitrary Python expressions from a string-based or compiled-code-based input. This function can be handy when you’re trying to dynamically evaluate Python expressions from any input that comes as a string or a compiled code object. In this case, you are overly complicating the problem. This is how I would accomplish what you want.
import datetime as dt
from datetime import timedelta
today = dt.date.today()
yesterday = today + timedelta(days=-1) #Note changed from your original
cntl_dict={'year':'year%100', 'month':'month', 'day':'day'}
inrecord = {'today':today, 'yesterday':yesterday}
for k, v in inrecord.items():
for l, w in cntl_dict.items():
print(f'{k}-{l}: {eval(f"{k}.{w}")}')
Yields:
today_year': 20
today_month': 12
today_day': 31
yesterday_year': 20
yesterday_month': 12
yesterday_day': 30

Related

How to specify string format with reference to the variable type at the time of formatting?

If there is a variable a whose type is unknown until it is created, how to account for the type when using python string formatting, specifically at the time of formatting?
Example:
import numpy as np
import datetime as dt
a = dt.datetime(2020,1,1)
print(f'{a:%d/%m/%Y}')
>>>
01/01/2020
However if the variable is changed, then this will produce an error:
a = np.nan
print(f'{a:%d/%m/%Y}')
>>>
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-70-02594a38949f> in <module>
1 a = np.nan
----> 2 print(f'{a:%d/%m/%Y}')
ValueError: Invalid format specifier
So I am attempting to implement something like:
print(f'{a:%d/%m/%Y if not np.isnan(a) else ,.0f}')
>>>
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-78-52c24b6abb16> in <module>
----> 1 print(f'{a:%d/%m/%Y if not np.isnan(a) else ,.0f}')
ValueError: Invalid format specifier
However, as we can see it attempts to format the variable before evaluating the expression. Perhaps the syntax needs correcting or another approach is required entirely. However, I do specifically want to evaluate which formatter to deploy it and deploy it at the time of printing the f-string.
Thanks!
Realise that the following approach is possible:
print(f'{a:%d/%m/%Y}' if isinstance(a,dt.datetime) else f'{a}')
However, the syntax is still too cumbersome, especially if this is to be deployed in mutliple locations. Effectively am searching for a streamlined way to format the variable if it is of a compatible type to the format and if not, then default to no specified formatter.
Thanks!
What about a custom function, so that you can handle all the types you need in a single point in your code?
import numbers
def get_formatter(var):
"""Get the format of a variable to be used in an f-string, based on its type"""
if isinstance(var, (dt.datetime, dt.date)):
return '%d/%m/%Y'
elif isinstance(var, numbers.Number):
if np.isfinite(var):
return '.3f'
return ''
You can then you the function to format the variable; for instance:
for v in [
dt.datetime(2020,1,1),
np.nan,
5,
5.555555,
]:
print(f'{v:{get_formatter(v)}}')
will produce:
01/01/2020
nan
5.000
5.556
I'd recommend to define your own custom formatter if you have many such kind of things to do.
For example:
import string
import datetime as dt
class CustomFormatter(string.Formatter):
def format_field(self, value, format_spec):
if isinstance(value, float):
return str(value)
elif isinstance(value,dt.datetime):
return value.__format__(format_spec)
return super().format(value, format_spec)
In this custom formatter, you can ignore all the format specifiers for floats and call the __format__() method for datetimes or change them to anything else you want. And while using, you just:
print(fmt.format("{:%d/%m/%Y}",a))
This is useful when you'd like use like this:
a = dt.datetime(2020,1,1)
b = np.nan
fmt = CustomFormatter()
for value in [a,b]:
print(fmt.format("The value is: {:%d/%m/%Y}",value))
The output will be:
The value is: 01/01/2020
The value is: nan
which means you do not need to change your specifier for datetime or float or any possible types and not even checking the types, cause you've done this and put it in your class.
You could make an inline conditional format like so:
print(f'{a:{"%d/%m/%Y" if not np.isnan(a) else ",.0f"}}')
So basically you needed an extra pair of curly brackets, however, I think the solution proposed by #PieCot is much cleaner.

How to get 1st calendar day of the current and next month based on a current date variable

I have a date variable calls today_date as below. I need to get the 1st calendar day of the current and next month.
In my case, today_date is 4/17/2021, I need to create two more variables calls first_day_current which should be 4/1/2021, and first_day_next which should be 5/1/2021.
Any suggestions are greatly appreciated
import datetime as dt
today_date
'2021-04-17'
Getting just the first date of a month is quite simple - since it equals 1 all the time. You can even do this without needing the datetime module to simplify calculations for you, if today_date is always a string "Year-Month-Day" (or any consistent format - parse it accordingly)
today_date = '2021-04-17'
y, m, d = today_date.split('-')
first_day_current = f"{y}-{m}-01"
y, m = int(y), int(m)
first_day_next = f"{y+(m==12)}-{m%12+1}-01"
If you want to use datetime.date(), then you'll anyway have to convert the string to (year, month, date) ints to give as arguments (or do today_date = datetime.date.today().
Then .replace(day=1) to get first_day_current.
datetime.timedelta can't add months (only upto weeks), so you'll need to use other libraries for this. But it's more imports and calculations to do the same thing in effect.
I found out pd.offsets could accomplish this task as below -
import datetime as dt
import pandas as pd
today_date #'2021-04-17' this is a variable that is being created in the program
first_day_current = today_date.replace(day=1) # this will be 2021-04-01
next_month = first_day_current + pd.offsets.MonthBegin(n=1)
first_day_next = next_month.strftime('%Y-%m-%d') # this will be 2021-05-01

adding ten days to datetime

I want to add 10 days to s so I try the following
import datetime
s= '01/11/2018'
add = s + datetime.timedelta(days = 10)
But I get an error
TypeError: must be str, not datetime.timedelta
so I try
add = s + str(datetime.timedelta(days = 10))
And I get
'01/11/201810 days, 0:00:00'
But this is not what I am looking for.
I would like the following output where 10 days are added to s
'01/21/2018'
I have also looked Adding 5 days to a date in Python but this doesnt seem to work for me
How do I get my desired output?
Your s is a string, not a datetime. Python knows how to add a string to a string and a datetime to a timedelta, but is pretty confused about you wanting to add a string and a timedelta.
datetime.datetime.strptime('01/11/2018', '%m/%d/%Y') + datetime.timedelta(days = 10)

How fix the error "ValueError: Julian Day must be positive" in netCDF4.num2date?

Here is the partial code:
import netCDF4
import pandas as pd
import matplotlib.pyplot as plt
file='/Users/dedeco/Downloads/_grib2netcdf-atls12-95e2cf679cd58ee9b4db4dd119a05a8d-OzkfHp.nc'
nc = netCDF4.Dataset(file)
nc.variables.keys()
lat = nc.variables['latitude'][:]
lon = nc.variables['longitude'][:]
time_var = nc.variables['time']
dtime = netCDF4.num2date(time_var[:],time_var.units)
The file can be download in the link: https://stream.ecmwf.int/data/atls12/data/data01/scratch/84/bc/_grib2netcdf-atls12-95e2cf679cd58ee9b4db4dd119a05a8d-OzkfHp.nc
So, I got this error:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-47-3647c36af24c> in <module>()
2 lon = nc.variables['longitude'][:]
3 time_var = nc.variables['time']
----> 4 dtime = netCDF4.num2date(time_var[:],time_var.units)
cftime/_cftime.pyx in cftime._cftime.num2date()
cftime/_cftime.pyx in cftime._cftime.utime.num2date()
cftime/_cftime.pyx in cftime._cftime.DateFromJulianDay()
ValueError: Julian Day must be positive
How I can fix? Any ideas?
I fixed the problem set the parameter (the default is standard): calendar: describes the calendar used in the time calculations.
So replace this:
dtime = netCDF4.num2date(time_var[:],time_var.units)
by (in this case the year has 365 days):
dtime = netCDF4.num2date(time_var[:],time_var.units,'365_day')
Here is the documentation as follow:
def date2num(
...)
date2num(dates,units,calendar='standard')
Return numeric time values given datetime objects. The units of the
numeric time values are described by the units argument and the
calendar keyword. The datetime objects must be in UTC with no
time-zone offset. If there is a time-zone offset in units, it will be
applied to the returned numeric values.
dates: A datetime object or a sequence of datetime objects. The
datetime objects should not include a time-zone offset.
units: a string of the form since
describing the time units. can be days, hours, minutes,
seconds, milliseconds or microseconds. is the time
origin.
calendar: describes the calendar used in the time calculations. All
the values currently defined in the CF metadata convention Valid
calendars 'standard', 'gregorian', 'proleptic_gregorian' 'noleap',
'365_day', '360_day', 'julian', 'all_leap', '366_day'. Default is
'standard', which is a mixed Julian/Gregorian calendar.
returns a numeric time value, or an array of numeric time values with
approximately millisecond accuracy.
A complementar understand about the conversion can be found here.

Sort by datetime in python3

Looking for help on how to sort a python3 dictonary by a datetime object (as shown below, a value in the dictionary) using the timestamp below.
datetime: "2018-05-08T14:06:54-04:00"
Any help would be appreciated, spent a bit of time on this and know that to create the object I can do:
format = "%Y-%m-%dT%H:%M:%S"
# Make strptime obj from string minus the crap at the end
strpTime = datetime.datetime.strptime(ts[:-6], format)
# Create string of the pieces I want from obj
convertedTime = strpTime.strftime("%B %d %Y, %-I:%m %p")
But I'm unsure how to go about comparing that to the other values where it accounts for both day and time correctly, and cleanly.
Again, any nudges in the right direction would be greatly appreciated!
Thanks ahead of time.
Datetime instances support the usual ordering operators (< etc), so you should order in the datetime domain directly, not with strings.
Use a callable to convert your strings to timezone-aware datetime instances:
from datetime import datetime
def key(s):
fmt = "%Y-%m-%dT%H:%M:%S%z"
s = ''.join(s.rsplit(':', 1)) # remove colon from offset
return datetime.strptime(s, fmt)
This key func can be used to correctly sort values:
>>> data = {'s1': "2018-05-08T14:06:54-04:00", 's2': "2018-05-08T14:05:54-04:00"}
>>> sorted(data.values(), key=key)
['2018-05-08T14:05:54-04:00', '2018-05-08T14:06:54-04:00']
>>> sorted(data.items(), key=lambda item: key(item[1]))
[('s2', '2018-05-08T14:05:54-04:00'), ('s1', '2018-05-08T14:06:54-04:00')]

Resources