Why datetime isocalendar transition back will shift 1 week? - python

I start datetime.now() (today is 6/11) and transition to week day:
>>> now=datetime.now().isocalendar()
>>> now
(2019, 24, 2)
but when I transition back, I found it shift 1 week:
>>> res = datetime.strptime(now[0]+'_'+now[1]+'_'+now[2], "%Y_%W_%w")
>>> res
datetime.datetime(2019, 6, 18, 0, 0)
can someone enplane it?
very thanks!

from datetime import daytime
iso_string = str(now[0]).zfill(4) + '_' + str(now[1]).zfill(2) + '_' + str(now[2])
res = datetime.strptime(iso_string, "%G_%V_%u").date()
strptime has special directives for ISO week numbers: "%G_%V_%u"
From trying out, it seems like leading zeros don't really matter, but the official standard says they do, so be sure to add them with zfill().
This only works for python>=3.6, previous versions of strptime seem to be incompatible with ISO weeks.
For earlier versions of python, I think it is best practice to use the isoweek module:
from isoweek import Week
res = Week(now[0], now[1]).day(now[2]-1)

Related

Python strptime errors on parsing strftime output

I am trying to store a date in a human readable format. For that I save and read back a string containing a date. I am using date.min to denote a date before any other.
from datetime import datetime, date
d = date.min
s = datetime.strftime(d, "%Y-%m-%d")
print(s)
# 1-01-01
d2 = datetime.strptime(s, "%Y-%m-%d")
# ValueError: time data '1-01-01' does not match format '%Y-%m-%d'
However, when I try to parse the date using strptime that was output by strftime, I only get an error. It seems that strptime is expecting leading zeros like 0001, which strftime is not outputting.
It might be possible to use None. Are there any other ways to work around what seems like a bug to me?
You need to add leading zeros:
try replacing:
s = {datetime.strftime(d, "%Y-%m-%d")}
with:
s = f'{d.year:04d}-{datetime.strftime(d, "%m-%d")}'
If you want to work with dates easily, I can really suggest the 'Arrow' library.
https://pypi.org/project/arrow/
Python 3.9 on Linux exhibits the problem as expected in the many comments on the question. One workaround that should work on all platforms is to use ISO format for the date string with date.isoformat():
>>> from datetime import date, datetime
>>> s = date.min.isoformat()
>>> s
'0001-01-01'
>>> d = datetime.strptime(s, "%Y-%m-%d")
>>> d
datetime.datetime(1, 1, 1, 0, 0)
>>> assert d.date() == date.min
You can also use date.fromisoformat() instead of strptime():
>>> date.fromisoformat(date.min.isoformat())
datetime.date(1, 1, 1)
The strftime can't add leading zeros to the year. It calls the underlying C function and its behavior on adding leading zeros to the year is platform specific. You can work around this by formatting the date object by yourself. Just do a check if d.year is less than 1000 and add how many leading zeros needed:
d = date.min
year_str = ''.join(['0' for _ in range(4 - len(str(d.year)))]) + str(d.year)
s = '{year}-{md}'.format(year=year_str, md=datetime.strftime(d, "%m-%d"))
d2 = datetime.strptime(s, "%Y-%m-%d")
# now d2 is equal to datetime.datetime(1, 1, 1, 0, 0)

Adding a timedelta to a skyfield Time

The skyfield Almanach documentation
uses this code to define the points in time between which to compute sunrise & sunset:
t0 = ts.utc(2018, 9, 12, 4)
t1 = ts.utc(2018, 9, 13, 4)
What if I just wanted to use one (start) date and set the next date to be exactly one day after? I can't just add one to the day argument since this would not be correct at the end of the month.
Using Python's datetime I could do this using
from datetime import datetime, timedelta
datetime(2019, 1, 31, 12) + timedelta(days=1)
# datetime.datetime(2019, 2, 1, 12, 0)
but I can't find anything like timedelta in the skyfield API documentation.
What if I just wanted to use one (start) date and set the next date to be exactly one day after? I can't just add one to the day argument since this would not be correct at the end of the month.
Happily, you can just add one to the day! As the documentation says:
https://rhodesmill.org/skyfield/time.html
"you are free to provide out-of-range values and leave it to Skyfield to work out the correct result"
>>> from skyfield.api import load
>>> ts = load.timescale()
[#################################] 100% deltat.data
>>> t = ts.utc(2018, 2, 28 + 1)
>>> t.utc_jpl()
'A.D. 2018-Mar-01 00:00:00.0000 UT'
You can use datetime's timedelta and convert back between datetime and skyfield's Time objects like this:
t0 = ts.utc(2019, 1, 31, 12)
t1 = ts.utc(t0.utc_datetime() + timedelta(days=1))
# Print
t1.utc_iso()
# '2019-02-01T12:00:00Z'
While certainly not beautiful, this allows you to use all the features of Python's datetime.

Format Pandas datetime column as year-week [duplicate]

Please what's wrong with my code:
import datetime
d = "2013-W26"
r = datetime.datetime.strptime(d, "%Y-W%W")
print(r)
Display "2013-01-01 00:00:00", Thanks.
A week number is not enough to generate a date; you need a day of the week as well. Add a default:
import datetime
d = "2013-W26"
r = datetime.datetime.strptime(d + '-1', "%Y-W%W-%w")
print(r)
The -1 and -%w pattern tells the parser to pick the Monday in that week. This outputs:
2013-07-01 00:00:00
%W uses Monday as the first day of the week. While you can pick your own weekday, you may get unexpected results if you deviate from that.
See the strftime() and strptime() behaviour section in the documentation, footnote 4:
When used with the strptime() method, %U and %W are only used in calculations when the day of the week and the year are specified.
Note, if your week number is a ISO week date, you'll want to use %G-W%V-%u instead! Those directives require Python 3.6 or newer.
In Python 3.8 there is the handy datetime.date.fromisocalendar:
>>> from datetime import date
>>> date.fromisocalendar(2020, 1, 1) # (year, week, day of week)
datetime.date(2019, 12, 30, 0, 0)
In older Python versions (3.7-) the calculation can use the information from datetime.date.isocalendar to figure out the week ISO8601 compliant weeks:
from datetime import date, timedelta
def monday_of_calenderweek(year, week):
first = date(year, 1, 1)
base = 1 if first.isocalendar()[1] == 1 else 8
return first + timedelta(days=base - first.isocalendar()[2] + 7 * (week - 1))
Both works also with datetime.datetime.
To complete the other answers - if you are using ISO week numbers, this string is appropriate (to get the Monday of a given ISO week number):
import datetime
d = '2013-W26'
r = datetime.datetime.strptime(d + '-1', '%G-W%V-%u')
print(r)
%G, %V, %u are ISO equivalents of %Y, %W, %w, so this outputs:
2013-06-24 00:00:00
Availabe in Python 3.6+; from docs.
import datetime
res = datetime.datetime.strptime("2018 W30 w1", "%Y %W w%w")
print res
Adding of 1 as week day will yield exact current week start. Adding of timedelta(days=6) will gives you the week end.
datetime.datetime(2018, 7, 23)
If anyone is looking for a simple function that returns all working days (Mo-Fr) dates from a week number consider this (based on accepted answer)
import datetime
def weeknum_to_dates(weeknum):
return [datetime.datetime.strptime("2021-W"+ str(weeknum) + str(x), "%Y-W%W-%w").strftime('%d.%m.%Y') for x in range(-5,0)]
weeknum_to_dates(37)
Output:
['17.09.2021', '16.09.2021', '15.09.2021', '14.09.2021', '13.09.2021']
In case you have the yearly number of week, just add the number of weeks to the first day of the year.
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> week = 40
>>> year = 2019
>>> date = datetime.date(year,1,1)+relativedelta(weeks=+week)
>>> date
datetime.date(2019, 10, 8)
Another solution which worked for me that accepts series data as opposed to strptime only accepting single string values:
#fw_to_date
import datetime
import pandas as pd
# fw is input in format 'YYYY-WW'
# Add weekday number to string 1 = Monday
fw = fw + '-1'
# dt is output column
# Use %G-%V-%w if input is in ISO format
dt = pd.to_datetime(fw, format='%Y-%W-%w', errors='coerce')
Here's a handy function including the issue with zero-week.

With the parsedatetime library in Python, is it possible to restrict a date to the current year?

Using parsedatetime, I'd like to pass a value like Jan 1 to the calendar parser and have it return Jan 1st of the current year (which, as I post this, would be 2014-01-01).
By default, parsedatetime returns the next occurrence of the date (i.e. 2015-01-01):
>>> import parsedatetime as pdt
>>> from datetime import datetime
>>> from time import mktime
>>> cal = pdt.Calendar()
>>> datetime.now()
datetime.datetime(2014, 8, 1, 15, 41, 7, 486294)
>>> str(datetime.fromtimestamp(mktime(cal.parse('Jan 1')[0])))
'2015-01-01 14:41:13'
>>> str(datetime.fromtimestamp(mktime(cal.parse('Dec 31')[0])))
'2014-12-31 14:41:17'
I've tried inputs like last Jan 1 and Jan 1 this year without success.
Is there a way to tell the parser to return the current year's value?
Editing to add a couple requirements that weren't specified with original question:
Supports natural language processing (that's why I'm using parsedatetime)
Doesn't compromise other parsedatetime parsing functionality (like years other than current and values like yesterday and 6 months before 3/1)
Bear here - have no idea how to get my original SO profile back as I used to use ClaimID...
anywho - you can set a flag to cause parsedatetime to never go forward a year when parsing only month/day values...
import parsedatetime as pdt
ptc = pdt.Constants()
ptc.YearParseStyle = 0
cal = pdt.Calendar(ptc)
print cal.parse('Jan 1')
# ((2014, 1, 1, 15, 57, 32, 5, 214, 1), 1)
The parse function appears to take a sourceTime parameter that you can set to the 1st of the current year.
See https://bear.im/code/parsedatetime/docs/index.html
I would replace the year on your datetime object. For example :
str(datetime.fromtimestamp(mktime(cal.parse('Dec 31')[0])))
would become:
str(datetime.fromtimestamp(mktime(cal.parse('Dec 31')[0])).replace(year=datetime.today().year))
If you aren't tied to using that library (and maybe you are?) you could do it like this:
>>> import datetime
>>> datetime.datetime.now().replace(month=1, day=1).strftime("%Y-%m-%d %H:%M:%S")
'2014-01-01 22:55:56'
>>>
from datetime import date, datetime
d = datetime.strptime('Jan 1', '%b %d')
d = date(datetime.now().year, d.month, d.day)
gives datetime.date(2014, 1, 1) for d, which you can then format with
print d.strftime('%Y-%m-%d')
2014-01-01
An improved implementation based on Bear's answer.
Again this is constrained by the fact that, since this is being implemented within another DSL parser, natural_date can only accept a single string:
import parsedatetime as pdt
from datetime import datetime, date, timedelta
from time import mktime
def natural_date(human_readable):
human_readable = human_readable.lower()
# Flag to cause parsedatetime to never go forward
# https://stackoverflow.com/a/25098991/1093087
ptc = pdt.Constants()
ptc.YearParseStyle = 0
cal = pdt.Calendar(ptc)
result, parsed_as = cal.parse(human_readable)
if not parsed_as:
raise ValueError("Unable to parse %s" % (human_readable))
return date.fromtimestamp(mktime(result))
def test_natural_date():
cases = [
# input, expect
('jan 1', date(date.today().year, 1, 1)),
('dec 31', date(date.today().year, 12, 31)),
('yesterday', date.today() - timedelta(days=1)),
('3 months before 12/31', date(date.today().year, 9, 30))
]
for human_readable, expect in cases:
result = natural_date(human_readable)
print("%s -> %s" % (human_readable, result))
assert result == expect, human_readable
test_natural_date()
Credit also goes to Mark Ransom, who unearthed sourceTime parameter, which provided another way to solve this issue, although that solution was complicated by this issue.

How do I find the nth day of the next month in Python?

I am trying to get the date delta by subtracting today's date from the nth day of the next month.
delta = nth_of_next_month - todays_date
print delta.days
How do you get the date object for the 1st (or 2nd, 3rd.. nth) day of the next month. I tried taking the month number from the date object and increasing it by 1. Which is obviously a dumb idea because 12 + 1 = 13. I also tried adding one month to today and tried to get to the first of the month. I am sure that there is a much more efficient way of doing this.
The dateutil library is useful for this:
from dateutil.relativedelta import relativedelta
from datetime import datetime
# Where day is the day you want in the following month
dt = datetime.now() + relativedelta(months=1, day=20)
This should be straightforward unless I'm missing something in your question:
import datetime
now = datetime.datetime.now()
nth_day = 5
next_month = now.month + 1 if now.month < 12 else 1 # February
year = now.year if now.month < 12 else now.year+1
nth_of_next_month = datetime.datetime(year, next_month, nth_day)
print(nth_of_next_month)
Result:
2014-02-05 00:00:00
Using dateutil as suggested in another answer is a much better idea than this, though.
Another alternative is to use delorean library:
Delorean is a library that provides easy and convenient datetime
conversions in Python.
>>> from delorean import Delorean
>>> d = Delorean()
>>> d.next_month()
Delorean(datetime=2014-02-15 18:51:14.325350+00:00, timezone=UTC)
>>> d.next_month().next_day(2)
Delorean(datetime=2014-02-17 18:51:14.325350+00:00, timezone=UTC)
My approach to calculating the next month without external libraries:
def nth_day_of_next_month(dt, n):
return dt.replace(
year=dt.year + (dt.month // 12), # +1 for december, +0 otherwise
month=(dt.month % 12) + 1, # december becomes january
day=n)
This works for both datetime.datetime() and datetime.date() objects.
Demo:
>>> import datetime
>>> def nth_day_of_next_month(dt, n):
... return dt.replace(year=dt.year + (dt.month // 12), month=(dt.month % 12) + 1, day=n)
...
>>> nth_day_of_next_month(datetime.datetime.now(), 4)
datetime.datetime(2014, 2, 4, 19, 20, 51, 177860)
>>> nth_day_of_next_month(datetime.date.today(), 18)
datetime.date(2014, 2, 18)
Without using any external library, this can be achived as follows
from datetime import datetime, timedelta
def nth_day_of_next_month(n):
today = datetime.now()
next_month_dt = today + timedelta(days=32-today.day)
return next_month_dt.replace(day=n)

Categories