19

I am wondering how to solve this problem with basic Python (no libraries to be used): How can I calculate when one's 10000 day after their birthday will be (/would be)?

For instance, given Monday 19/05/2008, the desired day is Friday 05/10/2035 (according to https://www.durrans.com/projects/calc/10000/index.html?dob=19%2F5%2F2008&e=mc2)

So far I have done the following script:

years = range(2000, 2050)
lst_days = []
count = 0
tot_days = 0
for year in years:
    if((year % 400 == 0) or  (year % 100 != 0) and  (year % 4 == 0)):
        lst_days.append(366)
    else:
        lst_days.append(365)
while tot_days <= 10000:
        tot_days = tot_days + lst_days[count]
        count = count+1
print(count)

Which estimates the person's age after 10,000 days from their birthday (for people born after 2000). But how can I proceed?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Dimitris
  • 569
  • 3
  • 14
  • @Dimitris So nothing predefined, not even the Python standard library? And when you say "estimates", how correct to you want the result to be? – eandklahn Mar 06 '22 at 16:37
  • 2
    @eandklahn It is a program about my son (high school). I did not want to use any predefined libraries. Just loops and conditions. I presented this script because I wanted to show that I have done something so far. I wanted to avoid that the question appears like a student exercise looking for a solution:-)! If my code is misleading I can erase it. – Dimitris Mar 06 '22 at 16:48
  • 5
    It is misleading (to me) because it is not clear what's the problem. It seems like you have code that you claim does what you want, so what is the problem? – Tomerikoo Mar 06 '22 at 17:22
  • @Tomerikoo because I think that these are also relevant sources where there might be someone that can help with another perspective. Sure, the question asks about programming in Python, but OP is also "puzzled about how to proceed". So my thought was that codereview could be a good source for best practices when dealing with dates, and cs can be helpful in terms of a concrete algorithm for dealing with date addition. – eandklahn Mar 06 '22 at 19:35
  • @Tomerikoo the code presented is incomplete. It gives a year but not an exact date. And even at that it could be wrong, because it doesn't account for the difference between someone born on Jan 1 vs. Dec 31. – Mark Ransom Mar 07 '22 at 04:28
  • 1
    I wrote a similar program to this to celebrate the day when a good friend turned a gigasecond old. Caught him completely by surprise, and a good time was had by all. – Mark Ransom Mar 07 '22 at 04:33
  • 1
    (1) Does your son know about modular arithmetic? That would be **extremely** relevant here. In fact, if your son doesn't know about modular arithmetic yet, it's worth it making a first version of the program without modulos today, and then next year when he's learned about modulo, make a second version of the program using them. – Stef Mar 07 '22 at 12:45
  • 5
    (2) Avoid using the name `sum` to name your own variable in python. You might notice it appears in a different colour in the code: that's because it's the name of a builtin function in python. Shadowing the name of builtin functions in python is confusing, and can even have unexpected consequences. – Stef Mar 07 '22 at 12:46
  • @MarkRansom: 1 Gigasecond ~ 31.7098 years – smci Mar 08 '22 at 02:45
  • @Stef: I renamed OP's `sum` to `tot_days` to avoid shadowing the builtin function. – smci Mar 08 '22 at 02:49
  • @smci not quite, you forgot to account for leap years. And that doesn't help you figure out the day, does it? – Mark Ransom Mar 08 '22 at 02:53
  • @MarkRansom: I specifically wrote '~' which means approximately. The approximate number comes from Google, not me; you could post a better one here or report Google's error to them. – smci Mar 08 '22 at 03:03
  • @Stef I'm not sure modulo would be useful in this context, since the time periods are variable. – Mark Ransom Mar 08 '22 at 19:04
  • @MarkRansom I don't know what you mean by "variable". Of course modulo are extremely useful to calculate dates. 10000 % 7 = 4, which tells you that the day of the week in 10000 days will be the same as the day of the week in 4 days. 10000 % 365 = 145; now you only have to subtract the number of leap days from 145. It's all arithmetic, no need for a loop that examine each year one by one. There are even people who can do these calculations in their head! – Stef Mar 09 '22 at 09:28
  • @Stef "now you only have to subtract the number of leap days" - that's the problem though, isn't it? Modulo can't take you to the finish by itself, you still need more complex logic to reach your goal. Probably involving a loop. – Mark Ransom Mar 10 '22 at 13:58
  • @MarkRansom The "more complex logic" you mention consists in counting the number of multiples of 4, and the number of multiples of 100, and the number of multiples of 400, between two numbers. Counting multiples of a number between two bounds can be achieved relatively easily by division, without any loop. – Stef Mar 10 '22 at 14:00
  • @MarkRansom With more divisions. Hint: First figure out the number of days since December 31st of the previous year. There are approximately 30 days per month, so if you divide that number of days per 30, you'll get a first approximation of the month. With some more divisions and modulo you can fix that approximation into an exact value. As for the day of the month, instead of dividing by 30, take modulo 30. – Stef Mar 10 '22 at 14:23
  • 1
    @MarkRansom Let me refer you to a similar question: [Math to convert seconds since 1970 into date and vice versa](https://stackoverflow.com/questions/7960318/math-to-convert-seconds-since-1970-into-date-and-vice-versa). The accepted answer links to a paper which you might enjoy if you want a full explanation. The accepted answer also provides C++ code implementing the solution. In particular, function `civil_from_days` doesn't contain any loop. – Stef Mar 10 '22 at 14:24
  • @MarkRansom I actually have a poster on the wall in my bedroom with a "universal calendar". It contains only 31 days; by a system of colour-coding for the years and months, it can give the day of the week for any year in the future. You'll be convinced that it doesn't contain any loop (it's a static piece of paper) but the way the days are arranged on a grid is an implementation of division, and the colour-coding an implementation of modulo. – Stef Mar 10 '22 at 14:27
  • The existing answers are way too complicated. There are single (well-known) expressions to convert to and from [Julian date](https://en.wikipedia.org/wiki/Julian_day), e.g. *JDN = (1461 × (Y + 4800 + (M − 14)/12))/4 +(367 × (M − 2 − 12 × ((M − 14)/12)))/12 − (3 × ((Y + 4900 + (M - 14)/12)/100))/4 + D − 32075*. With a Julian date it is just a matter of adding 10,000 to it and convert back. – Peter Mortensen Apr 09 '22 at 20:58

4 Answers4

20

Using base Python packages only

On the basis that "no special packages" means you can only use base Python packages, you can use datetime.timedelta for this type of problem:

import datetime

start_date = datetime.datetime(year=2008, month=5, day=19)

end_date = start_date + datetime.timedelta(days=10000)

print(end_date.date())

Without any base packages (and progressing to the problem)

Side-stepping even base Python packages, and taking the problem forwards, something along the lines of the following should help (I hope!).

Start by defining a function that determines if a year is a leap year or not:

def is_it_a_leap_year(year) -> bool:
    """
    Determine if a year is a leap year

    Args:
        year: int

    Extended Summary:
        According to:
            https://airandspace.si.edu/stories/editorial/science-leap-year
        The rule is that if the year is divisible by 100 and not divisible by
        400, leap year is skipped. The year 2000 was a leap year, for example,
        but the years 1700, 1800, and 1900 were not.  The next time a leap year
        will be skipped is the year 2100.
    """
    if year % 4 != 0:

        return False

    if year % 100 == 0 and year % 400 != 0:

        return False

    return True

Then define a function that determines the age of a person (utilizing the above to recognise leap years):

def age_after_n_days(start_year: int,
                     start_month: int,
                     start_day: int,
                     n_days: int) -> tuple:
    """
    Calculate an approximate age of a person after a given number of days,
    attempting to take into account leap years appropriately.

    Return the number of days left until their next birthday

    Args:
        start_year (int): year of the start date
        start_month (int): month of the start date
        start_day (int): day of the start date
        n_days (int): number of days to elapse
    """

    # Check if the start date happens on a leap year and occurs before the
    # 29 February (additional leap year day)
    start_pre_leap = (is_it_a_leap_year(start_year) and start_month < 3)

    # Account for the edge case where you start exactly on the 29 February
    if start_month == 2 and start_day == 29:

        start_pre_leap = False

    # Keep a running counter of age
    age = 0

    # Store the "current year" whilst iterating through the days
    current_year = start_year

    # Count the number of days left
    days_left = n_days

    # While there is at least one year left to elapse...
    while days_left > 364:

        # Is it a leap year?
        if is_it_a_leap_year(current_year):

            # If not the first year
            if age > 0:

                days_left -= 366

            # If the first year is a leap year but starting after the 29 Feb...
            elif age == 0 and not start_pre_leap:

                days_left -= 365

            else:

                days_left -= 366

        # If not a leap year...
        else:

            days_left -= 365

        # If the number of days left hasn't dropped below zero
        if days_left >= 0:

            # Increment age
            age += 1

            # Increment year
            current_year += 1

    return age, days_left

Using your example, you can test the function with:

age, remaining_days = age_after_n_days(start_year=2000, start_month=5, start_day=19, n_days=10000)

Now you have the number of complete years that will elapse and the number of remaining days

You can then use the remaining_days to work out the exact date.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Blithering
  • 560
  • 5
  • 14
  • 3
    It's clearly mentioned in the question that no libraries are to be used. It's easily achievable through datetime. – Abhyuday Vaish Mar 06 '22 at 16:31
  • FYI: [leap years are more complicated](https://en.wikipedia.org/wiki/Leap_year). For instance, millennia, such as 2000, aren't one either. Time's a horror for programming, which is why datetime packages are so complicated. – Adriaan Feb 09 '23 at 14:42
2

If you import library datetime

import datetime
your_date = "01/05/2000"
(day, month, years) = your_date.split("/")
date = datetime.date(int(years), int(month), int(day))
date_10000 = date+datetime.timedelta(days=10000)
print(date_10000)

No library script

your_date = "20/05/2000"
(day, month, year) = your_date.split("/")
days = 10000
year = int(year)
month = int(month)
day = int(day)
end=False
#m1,m3,m5,m7,m8,m10,m12=31
#m2=28
#m4,m6,m9,m11=30
m=[31,28,31,30,31,30,31,31,30,31,30,31]
while end!=True:
    if(((year % 400 == 0) or  (year % 100 != 0) and  (year % 4 == 0)) and(days-366>=0)):   
        days-=366
        year+=1
    elif(((year % 400 != 0) or  (year % 100 != 0) and  (year % 4 != 0)) and(days-366>=0)):
        days-=365
        year+=1
    else:
        end=True
end=False
if(((year % 400 == 0) or  (year % 100 != 0) and  (year % 4 == 0))):   
    m[1]=29
else:
    m[1]=28
while end!=True:
    if(days-m[month]>=0):
        days-=m[month]
        if(month+1!=12):
            month+=1
        else:
            year+=1
            if(((year % 400 == 0) or  (year % 100 != 0) and  (year % 4 == 0))):   
                m[1]=29
            else:
                m[1]=28
            month=0
    else:
        end=True

if(day+days>m[month]):
    day=day+days-m[month]+1
    if(month+1!=12):
        month+=1
    else:
        year+=1
        if(((year % 400 == 0) or  (year % 100 != 0) and  (year % 4 == 0))):   
            m[1]=29
        else:
            m[1]=28
        month=0
else:
    day=day+days
print(day,"/",month,"/",year)
KruII
  • 88
  • 6
  • The first three lines can just be `date = datetime.datetime.strptime("01/05/2000", "%d/%m/%Y")` – Tomerikoo Mar 06 '22 at 17:19
  • @KruII Your answer shows `IndexError: list index out of range` for `08/12/2004`. – Abhyuday Vaish Mar 07 '22 at 13:43
  • FYI: [leap years are more complicated](https://en.wikipedia.org/wiki/Leap_year). For instance, millennia, such as 2000, aren't one either. Time's a horror for programming, which is why datetime packages are so complicated. – Adriaan Feb 09 '23 at 14:42
2

Here's a solution I came up with that involves no libraries or packages, just loops and conditionals (accounts for leap years):

def isLeapYear(years):
  if years % 4 == 0:
    if years % 100 == 0:
      if years % 400 == 0:
        return True
      else:
        return False
    else:
      return True
  else:
    return False

monthDays = [31,28,31,30,31,30,31,31,30,31,30,31]
sum = 0
sumDays = []
for i in monthDays:
  sumDays.append(365 - sum)
  sum += i

timeInp = input("Please enter your birthdate in the format dd/mm/yyyy\n")
timeInp = timeInp.split("/")
days = int(timeInp[0])
months = int(timeInp[1])
years = int(timeInp[2])
totDays = 10000

if totDays > 366:
  if isLeapYear(years):
    if months == 1 or months == 2:
      totDays -= (sumDays[months - 1] + 1 - days) + 1
    else:
      totDays -= (sumDays[months - 1] - days) + 1
  else:
    totDays -= (sumDays[months - 1] - days) + 1

  months = 1
  days = 1
  years += 1

while totDays > 366:
  if isLeapYear(years):
    totDays -= 366
  else:
    totDays -= 365
  years += 1

i = 0
while totDays != 0:
  if isLeapYear(years):
    monthDays[1] = 29
  else:
    monthDays[1] = 28

  if totDays >= monthDays[i]:
    months += 1
    totDays -= monthDays[i]
  elif totDays == monthDays[i]:
    months += 1
    totDays = 0
  else:
    days += totDays
    if days % (monthDays[i] + 1)!= days:
      days %= monthDays[i] + 1
      months += 1
    totDays = 0

  if months == 13:
    months = 1
    years += 1

  i += 1
  if i == 12:
    i = 0

print(str(days) + "/" + str(months) + "/" + str(years))

As the name suggests, isLeapYear() takes in a parameter years, and returns a boolean value.

Our first step to this problem, to make it easier, is to just first "translate" our date to the next year. This makes our future calculations easier. To do this, we can define an array sumDays that stores the amount of days each month takes to finish the year (go to new years). Then, we subtract this amount from totDays, account for leap years, and update our variables.

Next, is the easy part, just skipping forward by the years while we have enough days for a complete year.

Once we can not add another full year, we just go month by month until we run out of days.

Sample Test Cases:

Input #1:

19/05/2008

Output #1:

5/10/2035

Input #2:

05/05/2020

Output #2:

21/9/2047

Input #3:

29/02/2020

Output #3:

17/7/2047

I checked most of my solutions with this website: https://www.countcalculate.com/calendar/birthday-in-days/result

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Aniketh Malyala
  • 2,650
  • 1
  • 5
  • 14
  • The "if forest" in the `isLeapYear` function is surprisingly hard to parse with a human eye. Condensing it into one boolean expression with `and` and `or` would probably be a lot more understandable and easier to maintain. – Stef Mar 07 '22 at 12:53
  • FYI: [leap years are more complicated](https://en.wikipedia.org/wiki/Leap_year). For instance, millennia, such as 2000, aren't one either. Time's a horror for programming, which is why datetime packages are so complicated. – Adriaan Feb 09 '23 at 14:42
  • Wow, it's been almost a year since I coded this haha. OP said that they did not want to use any packages, so I assumed they just wanted to manually code everything. But yeah, the rules for leap years are very annoying to code – Aniketh Malyala Feb 10 '23 at 15:02
1

I have updated my code for the leap year and month dates. Here is the code that I'm using:

n = input("Enter your DOB:(dd/mm/yyyy)")
d,m,y = n.split('/') #Splitting the DOB
d,m,y = int(d), int(m), int(y)
def if_leap(year): # Checking for leap year.
    if year % 4 != 0:
        return False
    elif year % 100 == 0 and year % 400 != 0:
        return False
    else:
        return True


target = 10000
while target > 364: # getting no.of years
    if if_leap(y):
        target -= 366
        y += 1
    else:
        target -= 365
        y += 1

while target > 27: # getting no. of months
    if m == 2 :
        if if_leap(y):
            target -= 29
            m += 1
            if m >= 12: # Resetting the month to 1 if it's value is greater than 12
                y += 1
                m -= 12
        else:
            target -= 28
            m += 1
            if m >= 12:
                y += 1
                m -= 12
    elif m in [1, 3, 5, 7, 8, 10, 12]:
        target -= 31
        m += 1
        if m >= 12:
            y += 1
            m -= 12
    elif m in [4, 6, 9, 11]:
        target -= 30
        m += 1
        if m >= 12:
            y += 1
            m -= 12
            
d = d + target # getting the no. of days
if d > 27:
    if m == 2:
        if if_leap(y):
            d -= 29
            m += 1
        else:
            d -= 28
            m += 1
    elif m in [1, 3, 5, 7, 8, 10, 12]:
        d -= 31
        m += 1
    else:
        d -= 30
        m += 1

print(f"The 10000th date will be {d}/{m}/{y}") 

Output:

Enter your DOB:(dd/mm/yyyy): 06/01/2006
The 10000th date will be 24/5/2033

P.S: I am getting some slightly different outputs when checking with that website. Can anyone figure out the bug/mistake in the code? It'll be really helpful. For eg. for the date 08/12/2004, it should be 25/4/2032 but my output is showing 24/4/2032.

Abhyuday Vaish
  • 2,357
  • 5
  • 11
  • 27
  • FYI: [leap years are more complicated](https://en.wikipedia.org/wiki/Leap_year). For instance, millennia, such as 2000, aren't one either. Time's a horror for programming, which is why datetime packages are so complicated. – Adriaan Feb 09 '23 at 14:43
  • @Adriaan Thanks for the info. I'll definitely take a look at that! – Abhyuday Vaish Feb 10 '23 at 04:18