Getting upcoming birthdays using 'date of birth' DateField - django

I'm trying to get the birthdays in the upcoming 20 days, given the below Person model:
class Person(models.Model):
dob = models.DateField() # date of birth
There are similar questions on SO already (here and here), but these do not cover my use case, as I'm storing a date of birth instead of the next birthday or a timefield.
I've tried to do some things like the following:
from datetime import timedelta, date
today = date.today()
next_20_days = today+timedelta(days=20)
Person.objects.filter(dob__month=today.month, dob__day__range=[today.day, next_20_days.day])
... but I get FieldError: Unsupported lookup 'day' for DateField or join on the field not permitted.
When I do e.g. Person.objects.filter(dob__month=today.month, dob__day=next_20_days.day), I do get the results for exactly 20 days from now. So I potentially could go over each of the 20 days in a loop, but that seems rather ineffective.
Any idea on how to do this the proper way?

FYI, I ended up doing the following which works for me and which does not require raw SQL.
Any improvements would be welcomed :-)
# Get the upcoming birthdays in a list (which is ordered) for the amount of days specified
def get_upcoming_birthdays(person_list, days):
person_list= person_list.distinct() # ensure persons are only in the list once
today = date.today()
doblist = []
doblist.extend(list(person_list.filter(dob__month=today.month, dob__day=today.day)))
next_day = today + timedelta(days=1)
for day in range(0, days):
doblist.extend(list(person_list.filter(dob__month=next_day.month, dob__day=next_day.day, dod__isnull=True)))
next_day = next_day + timedelta(days=1)
return doblist

Caveat: I believe calendars and time is hard. As a result, I feel obligated to warn you that I haven't rigorously tested my proposal. But of course, I think it should work. :)
Unfortunately, I think you should abandon date objects as the additional complication of year data precludes easy selects. Rather, I propose storing the birthday as a MMDD string (comparison of strings works, as long as you format them consistently). You can then compute your next_20_days and convert that to a similar MMDD string, as well as today, then use them as values to compare against.
I have three edge cases you should definitely make sure work:
Normal month rollover. (e.g., June to July)
Leap days -- don't forget to check presence as well as absence of Feb 29.
Year boundary -- you'll need to either do two queries and union the results, or do an OR query using Q objects.
Edit: See also:
How to store birthdays without a year part?
SQL Select Upcoming Birthdays
mySQL SELECT upcoming birthdays
and so on. I just did a Google search for "stack overflow birthday select".

I have been struggling with the same issue for the past days. I think I assembled a pretty solid solution that should allow you easily to derive all the birthdays to come up for the next X days. This query runs against the database-table geburtstage (birthdays) with the following 4 fields: ID (set as primary key) vorname (firstname), nachname (lastname) and geburtstag (birthday). Just create the table, fill in some records and run the query below:
select * FROM (
select curdate() AS today, DAY(CURDATE()) AS d_T, MONTH(CURDATE()) AS m_T, DAY(geburtstag) AS d_G, MONTH(geburtstag) AS m_G, subdate(CURDATE(),-20) AS date_20, DAY(subdate(CURDATE(),-20)) AS d_20, MONTH(subdate(CURDATE(),-20)) AS m_20, vorname, nachname, geburtstag, (YEAR(CURRENT_TIMESTAMP) - YEAR(geburtstag) +1 - CASE WHEN MONTH(CURRENT_TIMESTAMP) < MONTH(geburtstag) THEN 1 WHEN MONTH(CURRENT_TIMESTAMP) > MONTH(geburtstag) THEN 0 WHEN DAY(CURRENT_TIMESTAMP) <= DAY(geburtstag) THEN 1 ELSE 0 END) AS age, datediff(DATE_FORMAT(geburtstag,concat('%',YEAR(CURDATE()),'-%m-%d')),NOW()) AS no_of_days FROM geburtstage
union
select curdate() AS today, DAY(CURDATE()) AS d_T, MONTH(CURDATE()) AS m_T, DAY(geburtstag) AS d_G, MONTH(geburtstag) AS m_G, subdate(CURDATE(),-20) AS date_20, DAY(subdate(CURDATE(),-20)) AS d_20, MONTH(subdate(CURDATE(),-20)) AS m_20, vorname, nachname, geburtstag, (YEAR(CURRENT_TIMESTAMP) - YEAR(geburtstag) +1 - CASE WHEN MONTH(CURRENT_TIMESTAMP) < MONTH(geburtstag) THEN 1 WHEN MONTH(CURRENT_TIMESTAMP) > MONTH(geburtstag) THEN 0 WHEN DAY(CURRENT_TIMESTAMP) <= DAY(geburtstag) THEN 1 ELSE 0 END) AS age, datediff(DATE_FORMAT(geburtstag,concat('%',(YEAR(CURDATE())+1),'-%m-%d')),NOW()) AS no_of_days FROM geburtstage) AS upcomingbirthday
WHERE no_of_days >=0 AND no_of_days <= 20 GROUP BY ID
ORDER BY (m_G, d_G) < (m_T, d_T), m_G, d_G, geburtstag desc, age

Related

For Loop and If Statement not performing as expected

Here's the code:
# Scrape table data
alltable = driver.find_elements_by_id("song-table")
date = date.today()
simple_year_list = []
complex_year_list = []
dateformat1 = re.compile(r"\d\d\d\d")
dateformat2 = re.compile(r"\d\d\d\d-\d\d-\d\d")
for term in alltable:
simple_year = dateformat1.findall(term.text)
for year in simple_year:
if 1800 < int(year) < date.year: # Year can't be above what the current year is or below 1800,
simple_year_list.append(simple_year) # Might have to be changed if you have a song from before 1800
else:
continue
complex_year = dateformat2.findall(term.text)
complex_year_list.append(complex_year)
The code uses regular expressions to find four consecutive digits. Since there are multiple 4 digit numbers, I want to narrow it down to between 1800 and 2021 since that's a reasonable time frame. simple_year_list, however, prints out numbers that don't follow the conditions.
You aren't saving the right value here:
simple_year_list.append(simple_year)
You should be saving the year:
simple_year_list.append(year)
I would need more information to help further though. Maybe give us a sample of the data you're working through, and the output you're seeing?
You can do it all in regex.
Add start ^ and end $ anchors, and range restriction via pattern:
dateformat1 = re.compile(r"^(1[89]\d\d|20([01]\d|2[01]))$")

Matching diverse dates in Openrefine

I am trying to use the value.match command in OpenRefine 2.6 for splitting the information presents in a column into (at least) 2 columns.
The data are, however, quite messed up.
I have sometimes full dates:
May 30, 1949
Sometimes full dates are combined with other dates and attributes:
May 30, 1949, published 1979
May 30, 1949 and 1951, published 1979
May 30, 1949, printed 1980
May 30, 1949, print executed 1988
May 30, 1949, prints executed 1988
published 1940
Sometimes you have timespan:
1905-05 OR 1905-1906
Sometimes only the year
1905
Sometimes year with attributes
August or September 1908
Doesn't seems to follow any specific schema or order.
I would like to extract (at least)ca start and end date year, in order to have two columns:
-----------------------
|start_date | end_date|
|1905 | 1906 |
-----------------------
without the rest of the attributes.
I can find the last date using
value.match(/.*(\d{4}).*?/)[0]
and the first one with
value.match(/.*^(\d{4}).*?/)[0]
but I got some trouble with the two formulas.
The latter cannot match anything in case of:
May 30, 1949 and 1951, published 1979
while in the case of:
Paris, winter 1911-12
The latter formula cannot match anything and the former formula match 1911
Anyone know how I can resolve the problem?
I would need a solution that take the first date as start_date and final date as end_date, or better (don't know if it is possible) earliest date as start_date and latest date as end_date.
Moreover, I would be glad to have some clue about how to extract other information, such as
if published or printed or executed is present in the text -> copy date to a new column name “execution”.
should be something like create a new column
if(value.match("string1|string2|string3" + (\d{4}), "perform the operation", do nothing)
value.match() is a very useful but sometimes tricky function. To extract a pattern from a text, I prefer to use Python/Jython's regular expressions :
import re
pattern = re.compile(r"\d{4}")
return pattern.findall(value)
From there, you can create a string with all the years concatenated:
return ",".join(pattern.findall(value))
Or select only the first:
return pattern.findall(value)[0]
Or the last:
return pattern.findall(value)[-1]
etc.
Same thing for your sub-question:
import re
pattern = re.compile(r"(published|printed|executed)\s+(\d+)")
return pattern.findall(value)[0][1]
Or :
import re
pattern = re.compile(r"(published|printed|executed)\s+(\d+)")
m = re.search(pattern, value)
return m.group(2)
Example:
Here is a regex which will extract start_date and end_date in named groups :
If there is only one date, then it consider it's the start_date :
((?<start_date>\d{4}).*?)?(?<end_date>\d{4}|(?<=-)\d{2})?$
Demo

juliandate to normaldate in redshift

I have date like 117106, 117107 in an column which is of numeric type in redshift data base. Understood that the format is in Julian format. I wanted to change it to normal date format like yyyymmdd.
I tried applying the function to the column and it returns the value as below
select to_date(117106) - result 4393-07-10
Please help.
Thanks in advance
Here is how it is done.
The way it works is the first 3 digits is the century julian offset, and the last 3 are the day offset:
select dateadd(day,117106 % 1000,dateadd(year,(117106 /1000),convert(datetime,'01/01/1900')))-1
If I’ve made a bad assumption please comment and I’ll refocus my answer.
Thank you Rahul for the help.
I haven't tried the solution provided.However i have implemented the below solution as below to convert it into date format
trim(cast(to_char(dateadd(days,cast(right(x)as bigint)
+ datediff(days,'1900-01-02',to_date(cast(left((1900+(x/1000)),4) as char(4)) || '-01' || '-01','yyyy-mm-dd')),'1900-01-01'),
'YYYYMMDD')as decimal),0) as x
Can generate_series() to cover the julian day range you need and then use standard date functions
with julian_day as (
select generate_series as julian_day,
to_date(generate_series, 'J') as gregorian_date
from generate_series((2459865 - ( 10 * 365)), (2459865 + (10 * 365)), 1)
)
select
julian_day,
gregorian_date,
to_char(gregorian_date, 'IYYY') as iso_year,
date_part(year, gregorian_date) as year,
...
from julian_day

Calculating julian date in python

I'm trying to create a julian date in python and having major struggles. Is there nothing out there as simple as:
jul = juliandate(year,month,day,hour,minute,second)
where jul would be something like 2457152.0 (the decimal changing with the time)?
I've tried jdcal, but can't figure out how to add the time component (jdcal.gcal2jd() only accepts year, month and day).
Not a pure Python solution but you can use the SQLite in memory db which has a julianday() function:
import sqlite3
con = sqlite3.connect(":memory:")
list(con.execute("select julianday('2017-01-01')"))[0][0]
which returns: 2457754.5
Here you go - a pure python solution with datetime and math library.
This is based on the the Navy's Astronomical Equation found here and verified with their own calculator: http://aa.usno.navy.mil/faq/docs/JD_Formula.php
import datetime
import math
def get_julian_datetime(date):
"""
Convert a datetime object into julian float.
Args:
date: datetime-object of date in question
Returns: float - Julian calculated datetime.
Raises:
TypeError : Incorrect parameter type
ValueError: Date out of range of equation
"""
# Ensure correct format
if not isinstance(date, datetime.datetime):
raise TypeError('Invalid type for parameter "date" - expecting datetime')
elif date.year < 1801 or date.year > 2099:
raise ValueError('Datetime must be between year 1801 and 2099')
# Perform the calculation
julian_datetime = 367 * date.year - int((7 * (date.year + int((date.month + 9) / 12.0))) / 4.0) + int(
(275 * date.month) / 9.0) + date.day + 1721013.5 + (
date.hour + date.minute / 60.0 + date.second / math.pow(60,
2)) / 24.0 - 0.5 * math.copysign(
1, 100 * date.year + date.month - 190002.5) + 0.5
return julian_datetime
Usage Example:
# Set the same example as the Naval site.
example_datetime = datetime.datetime(1877, 8, 11, 7, 30, 0)
print get_julian_datetime(example_datetime)
Answer one from Extract day of year and Julian day from a string date in python. No libraries required.
Answer two is a library from https://pypi.python.org/pypi/jdcal
The easiest: df['Julian_Dates']= df.index.to_julian_date(). you need to set your date time column to index (Use Pandas).
There is a way with using Astropy. First, change your time to a list (t). Second, change that list to astropy time (Time). Finally, compute your JD or MJD (t.jd t.mjd).
https://docs.astropy.org/en/stable/time/
For df:
t = Time(DF.JulianDates,format='jd',scale='utc')
A simple fudge the numbers script via wiki don't know either. Please note that this was written using Python 3.6 so I'm not sure it would work on Python 2.7 but this is also an old question.
def julian_day(now):
"""
1. Get current values for year, month, and day
2. Same for time and make it a day fraction
3. Calculate the julian day number via https://en.wikipedia.org/wiki/Julian_day
4. Add the day fraction to the julian day number
"""
year = now.year
month = now.month
day = now.day
day_fraction = now.hour + now.minute / 60.0 + now.second / 3600.0 / 24.0
# The value 'march_on' will be 1 for January and February, and 0 for other months.
march_on = math.floor((14 - month) / 12)
year = year + 4800 - march_on
# And 'month' will be 0 for March and 11 for February. 0 - 11 months
month = month + 12 * march_on - 3
y_quarter = math.floor(year / 4)
jdn = day + math.floor((month * 153 + 2) / 5) + 365 * year + y_quarter
julian = year < 1582 or year == (1582 and month < 10) or (month == 10 and day < 15)
if julian:
reform = 32083 # might need adjusting so needs a test
else:
reform = math.floor(year / 100) + math.floor(year / 400) + 32030.1875 # fudged this
return jdn - reform + day_fraction
Generally this was just to try for myself as the most common algorithm was giving me trouble. That works and if you search around for it and write your script using it as it comes in many languages. But this one has steps in the docs to try to keep it simple. The biggest decision is how often are you going to look for dates that are before Gregorian reform. That is why I never tested that yet but go ahead and play with it as it needs a lot of massaging. :-D At least I think is conforms to PEP8 even if it isn't up to best practices. Go ahead and pylint it.
You could just use source packages like PyEphem or whatever but you still would like to know what's going on with it so you could write your own tests. I'll link that PyEphem for you but there are lots of ready made packages that have Julian Day calculations.
Your best bet if you are doing lots of work with these types of numbers is to get a list of the constant ones such as J2000.
datetime.datetime(2000, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc)
datetime.datetime.toordinal() + 1721425 - 0.5 # not tested
# or even
datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
It's not so hard to figure these out if you get familiar with what datetime library does. Just for fun did you notice the PyEphem logo? I suspect it comes from something like this
One post that I saw seems to work but has no tests is jiffyclub
Now here is the more common way to calculate two values using a datetime object.
def jdn(dto):
"""
Given datetime object returns Julian Day Number
"""
year = dto.year
month = dto.month
day = dto.day
not_march = month < 3
if not_march:
year -= 1
month += 12
fr_y = math.floor(year / 100)
reform = 2 - fr_y + math.floor(fr_y / 4)
jjs = day + (
math.floor(365.25 * (year + 4716)) + math.floor(30.6001 * (month + 1)) + reform - 1524)
if jjs < ITALY:
jjs -= reform
return jjs
# end jdn
def ajd(dto):
"""
Given datetime object returns Astronomical Julian Day.
Day is from midnight 00:00:00+00:00 with day fractional
value added.
"""
jdd = jdn(dto)
day_fraction = dto.hour / 24.0 + dto.minute / 1440.0 + dto.second / 86400.0
return jdd + day_fraction - 0.5
# end ajd
It may not be the best practice in Python but you did ask how to calculate it not just get it or extract it although if that is what you want those questions have been answered as of late.
Try https://www.egenix.com/products/python/mxBase/mxDateTime/
First construct a DateTime object via the syntax
DateTime(year,month=1,day=1,hour=0,minute=0,second=0.0)
Then you can use '.jdn' object method to get the value you are looking for.

django models choices list - decending order output

I asked this SO question yesterday.
The code I now have to display the choices list in my models.py is:
YOB_TYPES = Choices(*(
((0, 'select_yob', _(' Select Year of Birth')),
(2000, 'to_present', _('2000 to Present'))) +
tuple((i, str(i)) for i in xrange(1990, 2000)) +
((1, 'unspecified', _('Prefer not to answer')),))
)
....
year_of_birth_type = models.PositiveIntegerField(choices=YOB_TYPES, default=YOB_TYPES.select_yob, validators=[MinValueValidator(1)])
....
The choices list is now displayed with the year of birth running from 1990 to 1999 (ascending order) as shown below:
How do I change the code so that the year of birth dates are displayed 1999 to 1990 (decending order) as shown below:
I have searched but cannot locate anything related to my issue - reversing ( .reverse() ) the tuple output - maybe I am searching the wrong topic.
See this line:
tuple((i, str(i)) for i in xrange(1990, 2000)) +
Adjust like so:
tuple((i, str(i)) for i in xrange(1999, 1989, -1) +
The third argument specifies your "step", in this case -1 (to go in reverse). Remember that with xrange, the second parameter is not included in the iteration, so use 1989 rather than 1990 (the same reason why you used 2000 earlier, rather than 1999).