Add/deduct month taking into account unequal days in calendar month - python-3.x

I am trying to come up with a way to create a list of dates n months back from given date dt. However, it seems to tricky based on what dt is. Below I am illustrating the dilemma through a few examples (esp. look at tricky case-3 below):
from datetime import datetime
from dateutil.relativedelta import relativedelta
# Simple case.
dt = datetime(2021, 2, 15)
dt - relativedelta(months=1) # n=1 gives datetime.datetime(2021, 1, 15, 0, 0)
dt - relativedelta(months=2) # n=2 gives datetime.datetime(2020, 12, 15, 0, 0)
# Simple case-2
dt = datetime(2021, 3, 31)
dt - relativedelta(months=1) # n=1 gives datetime.datetime(2021, 2, 28, 0, 0)
dt - relativedelta(months=2) # n=2 gives datetime.datetime(2021, 1, 31, 0, 0)
dt - relativedelta(months=3) # n=3 gives datetime.datetime(2020, 12, 31, 0, 0)
dt - relativedelta(months=4) # n=4 gives datetime.datetime(2020, 11, 30, 0, 0)
# Tricky case-3
dt = datetime(2021, 2, 28)
dt - relativedelta(months=1) # n=1 gives datetime.datetime(2021, 1, 28, 0, 0) and not datetime.datetime(2021, 1, 31, 0, 0)
dt - relativedelta(months=2) # n=2 gives datetime.datetime(2020, 12, 28, 0, 0) and not datetime.datetime(2020, 12, 31, 0, 0)
dt - relativedelta(months=3) # n=3 gives datetime.datetime(2020, 11, 28, 0, 0) and not datetime.datetime(2020, 11, 30, 0, 0)
dt - relativedelta(months=4) # n=4 gives datetime.datetime(2020, 10, 28, 0, 0) and not datetime.datetime(2020, 10, 31, 0, 0)

relativedelta seems to fail on the corner case of date is end of month while month has less than 31 days. Here's a work-around:
check if date is end of month
if not, simply use relativedelta
if so, use relativedelta but make sure the day is the last of the month by setting the day attribute explicitly
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
# add_month adds n months to datetime object dt
def add_month(dt, n):
# we can add a day without month changing - not end of month:
if (dt + timedelta(1)).month == dt.month:
return dt + relativedelta(months=n)
# implicit else: end of month
return (dt + relativedelta(months=n+1)).replace(day=1) - timedelta(1)
Examples:
d = datetime(2021, 3, 15)
print(add_month(d, -1).date(), d.date(), add_month(d, 1).date())
# 2021-02-15 2021-03-15 2021-04-15
d = datetime(2021, 3, 31)
print(add_month(d, -1).date(), d.date(), add_month(d, 1).date())
# 2021-02-28 2021-03-31 2021-04-30
d = datetime(2021,2,28)
print(add_month(d, -1).date(), d.date(), add_month(d, 1).date())
# 2021-01-31 2021-02-28 2021-03-31
d = datetime(2021,11,30)
print(add_month(d, -1).date(), d.date(), add_month(d, 1).date())
# 2021-10-31 2021-11-30 2021-12-31

Related

Create datetime object from numpy array floats

I have a numpy array which contains hours from 4 days:
s = np.array([0.0, 1.0, 2.0, 3.0, 4.0 ....96.0])
I want to create a datetime object from that.
I know that the first element is at timestamp 2021-03-21 00:00,
so:
start_date = datetime.datetime.strptime('2021-03-21 00:00', '%Y-%m-%d %H:%M')
How can I create a new array which contains datetimes, incremented by an hour from the s array.
Use timedelta to build your new array:
>>> import numpy as np
>>> from datetime import datetime, timedelta
>>> s = np.array([0.0, 1.0, 2.0, 3.0, 4.0, 96.0])
>>> start_date = datetime.strptime('2021-03-21 00:00', '%Y-%m-%d %H:%M')
>>> [start_date + timedelta(hours=diff) for diff in s]
[datetime.datetime(2021, 3, 21, 0, 0), datetime.datetime(2021, 3, 21, 1, 0), datetime.datetime(2021, 3, 21, 2, 0), datetime.datetime(2021, 3, 21, 3, 0), datetime.datetime(2021, 3, 21, 4, 0), datetime.datetime(2021, 3, 25, 0, 0)]

Construct date based on week_of_month and day_of_week criteria

I am not sure how to go about constructing datetime object given year, month, week_of_month and day_of_week. Any clues? Using this I am trying to achieve following:
From (start_month, start_year) to (end_month, end_year) find monthly dates as specified by week_of_month and day_of_week parameters. Here 1 <= week_of_month <= 5 and 1 <= day_of_week <= 7. Now,
Each month may not have 5 weeks (eg. February in non-leap year)
1st and 5th week may not have 7 days.
In such cases, based on boolean is_to_next_day, if True then specify next calendar day, if False then skip it.
Sample input/outputs:
Input parameters: start_month=1 start_year=2020, end_month=12, end_year=2020, week_of_month=5, day_of_week=3, is_to_next_day=True
Desired output: [datetime(2020, 1, 29), datetime(2020, 2, 26), datetime(2020, 3, 25), datetime(2020, 4, 29), datetime(2020, 5, 27), datetime(2020, 7, 1), datetime(2020, 7, 29), datetime(2020, 8, 26), datetime(2020, 9, 30), datetime(2020, 10, 28), datetime(2020, 11, 25), datetime(2020, 12, 30)]
Input parameters: start_month=1 start_year=2020, end_month=12, end_year=2020, week_of_month=5, day_of_week=3, is_to_next_day=False
Desired output: [datetime(2020, 1, 29), datetime(2020, 2, 26), datetime(2020, 3, 25), datetime(2020, 4, 29), datetime(2020, 5, 27), datetime(2020, 7, 29), datetime(2020, 8, 26), datetime(2020, 9, 30), datetime(2020, 10, 28), datetime(2020, 11, 25), datetime(2020, 12, 30)]
import calendar
from datetime import datetime
def get_date(year, month, week_of_month, day_of_week, is_to_next_day):
mnth = calendar.monthcalendar(year, month)
if (week_of_month > 1) and (week_of_month < 5):
day = mnth[week_of_month - 1][day_of_week - 1]
return datetime(year, month, day)
elif week_of_month == 1:
last_day_of_first_week = mnth[0][6]
if day_of_week <= last_day_of_first_week:
return datetime(year, month, day_of_week)
elif is_to_next_day:
return datetime(year, month, mnth[1][0])
else:
return None
else:
if (len(mnth) >= week_of_month):
day = mnth[week_of_month - 1][day_of_week - 1]
if(day==0) and is_to_next_day:
return datetime(year + int((month + 1)/12), (month + 1)%12, 1)
elif(day==0):
return None
else:
return datetime(year, month, day)
if (len(mnth) < week_of_month):
if is_to_next_day:
return datetime(year + int((month + 1)/12), (month + 1)%12, 1)
else:
return None
# First output
[get_date(yy, mm, 5, 3, True) for mm in range(1, 13) for yy in [2020]]
# Second output
[get_date(yy, mm, 5, 3, False) for mm in range(1, 13) for yy in [2020]] # Iterate again to drop None.

how to replace a time variable in a string to epoch

I'm playing around with getting a string with date format and values transformed to influxdb ready import.
Therefore I'm trying to transform the datetime to epoch timestamp in python.
What I've so far.
#Import of some modules
import re
from datetime import date
import time, os
#example text which I'll retrieve daily
txt = "daily power [{'date': datetime.datetime(2020, 12, 1, 0, 0), 'total_consumption_kwh': 0.483}, {'date': datetime.datetime(2020, 12, 2, 0, 0), 'total_consumption_kwh': 0.269}, {'date': datetime.datetime(2020, 11, 4, 0, 0), 'total_consumption_kwh': 0.611}, {'date': datetime.datetime(2020, 11, 5, 0, 0), 'total_consumption_kwh': 0.61}]"
#Regex patterns
date_regex = r'\d+,\s\d+,\s\d+'
consump_regex = r'\w+\':\s\d+.\d+'
regex_plug = r'\w+\(\d+\,\s\d+\,\s\d+\,\s\d+\,\s\d+\)\,\s\'\w+\'\:\s\d+\.\d+'
x= re.findall(regex_plug, txt)
nl = ("\n".join(x))
condition1 = str(nl).replace(",", "-")
condition2 = condition1.replace(" ", "",)
condition3 = condition2.replace("':", "': ")
condition4 = condition3.replace("(", "': ")
condition5 = condition4.replace(")-", " ")
condition6 = condition5.replace("d", "'d")
print(condition6)
results in:
'datetime': 2020-12-1-0-0 'total_consumption_kwh': 0.483
'datetime': 2020-12-2-0-0 'total_consumption_kwh': 0.269
'datetime': 2020-11-4-0-0 'total_consumption_kwh': 0.611
'datetime': 2020-11-5-0-0 'total_consumption_kwh': 0.61
The last step is to transform the time to epoch timestamp, but I don't get this working.
I found already some threads describing how to replace date to epoch. with
date_time = '2020-12-1-0-0' #should be txt
pattern = '%Y-%m-%d-%H-%M'
epoch = int(time.mktime(time.strptime(date_time, pattern)))
print(epoch)
But how can I process this over my condition6 string, to make the output:
'datetime': 1606780800 'total_consumption_kwh': 0.483
Using eval:
import datetime
txt = "daily power [{'date': datetime.datetime(2020, 12, 1, 0, 0), 'total_consumption_kwh': 0.483}, {'date': datetime.datetime(2020, 12, 2, 0, 0), 'total_consumption_kwh': 0.269}, {'date': datetime.datetime(2020, 11, 4, 0, 0), 'total_consumption_kwh': 0.611}, {'date': datetime.datetime(2020, 11, 5, 0, 0), 'total_consumption_kwh': 0.61}]"
daily_power = eval(txt.strip().strip("daily power"))
# Now just convert the datetimes to timestamps:
for d in daily_power:
d['date'] = d['date'].timestamp()
print(daily_power) # Yields wanted result.
An approach without using eval (due to safety etc):
from ast import literal_eval as leval
date_cons = re.findall(r'{[^\{\}]+}', txt)
for idx, dc in enumerate(date_cons):
dtnums = re.search(r'\((?:\d+,?\s*){5}\)', dc).group()
ts = datetime.datetime(*leval(dtnums)).timestamp()
date_cons[idx] = leval(re.sub('datetime\.datetime\(.+\)', str(ts), dc))
print(date_cons) # Yields wanted result.
If you dont even want to use literal_eval (which is safe) you can just use more regex.

Creating a function for my code with datetime and timedelta

I have finished my code and it works when I run it. However, I need to turn this into a function, that if I call the function and pass in any list of number, i can get the same results. This is my code:
from datetime import datetime, timedelta
dateStr = 'user-input'
dateObj = datetime.strptime(dateStr, '%Y%m%d')
timeStep = timedelta(days=1)
dateObj2 = dateObj + timeStep
days15 = [dateObj + timeStep*i for i in range(15)]
print(days15)
------------------ output:
datetime.datetime(2017, 1, 1, 0, 0),..
I need to be able to pass in
date_str = "20170817"
results = days_15(date_str)
print(results)
And then get the same results. Any hints? or any help - Thank you
You just need to add a def statement before your code to define the function, and rather than printing the result, return it:
from datetime import datetime, timedelta
def days_15(dateStr):
dateObj = datetime.strptime(dateStr, '%Y%m%d')
timeStep = timedelta(days=1)
return [dateObj + timeStep*i for i in range(15)]
my_date_str = "20170817"
results = days_15(my_date_str)
print(results)
Output:
[
datetime.datetime(2017, 8, 17, 0, 0), datetime.datetime(2017, 8, 18, 0, 0),
datetime.datetime(2017, 8, 19, 0, 0), datetime.datetime(2017, 8, 20, 0, 0),
datetime.datetime(2017, 8, 21, 0, 0), datetime.datetime(2017, 8, 22, 0, 0),
datetime.datetime(2017, 8, 23, 0, 0), datetime.datetime(2017, 8, 24, 0, 0),
datetime.datetime(2017, 8, 25, 0, 0), datetime.datetime(2017, 8, 26, 0, 0),
datetime.datetime(2017, 8, 27, 0, 0), datetime.datetime(2017, 8, 28, 0, 0),
datetime.datetime(2017, 8, 29, 0, 0), datetime.datetime(2017, 8, 30, 0, 0),
datetime.datetime(2017, 8, 31, 0, 0)
]

Imported Variable not defined

Currently trying to import my code from "Original Program" which is working fine independently into "Import". I am getting the error "Date is not defined". I assume the error is to do with global variables. I am knew to this and have spent some time trying to solve the issue myself to no avail. Any help appreciated :)
**Original Program**
def validDate(date, month, day):
months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
leapyear = ((date % 4) == 0) and ((date % 100) != 0) or ((date % 4) ==
0) and ((date % 100) == 0) and ((date % 4000) == 0)
if leapyear:
days[1] += 1
valid = (day <= days[months.index(month)] and day > 0 and (1753 <=
date))
print(valid)
date = int(input("Enter year: "))
month = int(input("Enter month: "))
day = int(input("Enter day: "))
validDate(date, month, day)
**Import**
from libHWDate import validDate
validDate(date, month, day)
remove the import command
from libHWDate import validDate

Resources