Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add YEAR(), MONTH() and EOMONTH() #175

Merged
merged 7 commits into from
Apr 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 54 additions & 7 deletions koala/excellib.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
from __future__ import absolute_import, division

import numpy as np
from datetime import datetime, date
import datetime
from math import log, ceil
from decimal import Decimal, ROUND_UP, ROUND_HALF_UP
from calendar import monthrange
from dateutil.relativedelta import relativedelta

from openpyxl.compat import unicode

Expand Down Expand Up @@ -99,11 +101,14 @@
"ROUNDUP",
"POWER",
"SQRT",
"TODAY"
"TODAY",
"YEAR",
"MONTH",
brianmay marked this conversation as resolved.
Show resolved Hide resolved
"EOMONTH",
]

CELL_CHARACTER_LIMIT = 32767
EXCEL_EPOCH = datetime.strptime("1900-01-01", '%Y-%m-%d').date()
EXCEL_EPOCH = datetime.datetime.strptime("1900-01-01", '%Y-%m-%d').date()

######################################################################################
# List of excel equivalent functions
Expand Down Expand Up @@ -419,6 +424,48 @@ def mod(nb, q): # Excel Reference: https://support.office.com/en-us/article/MOD-
return nb % q


def eomonth(start_date, months): # Excel reference: https://support.office.com/en-us/article/eomonth-function-7314ffa1-2bc9-4005-9d66-f49db127d628
if not is_number(start_date):
return ExcelError('#VALUE!', 'start_date %s must be a number' % str(start_date))
if start_date < 0:
return ExcelError('#VALUE!', 'start_date %s must be positive' % str(start_date))

if not is_number(months):
return ExcelError('#VALUE!', 'months %s must be a number' % str(months))

y1, m1, d1 = date_from_int(start_date)
start_date_d = datetime.date(year=y1, month=m1, day=d1)
end_date_d = start_date_d + relativedelta(months=months)
y2 = end_date_d.year
m2 = end_date_d.month
d2 = monthrange(y2, m2)[1]
res = int(int_from_date(datetime.date(y2, m2, d2)))

return res


def year(serial_number): # Excel reference: https://support.office.com/en-us/article/year-function-c64f017a-1354-490d-981f-578e8ec8d3b9
if not is_number(serial_number):
return ExcelError('#VALUE!', 'start_date %s must be a number' % str(serial_number))
if serial_number < 0:
return ExcelError('#VALUE!', 'start_date %s must be positive' % str(serial_number))

y1, m1, d1 = date_from_int(serial_number)

return y1


def month(serial_number): # Excel reference: https://support.office.com/en-us/article/month-function-579a2881-199b-48b2-ab90-ddba0eba86e8
if not is_number(serial_number):
return ExcelError('#VALUE!', 'start_date %s must be a number' % str(serial_number))
if serial_number < 0:
return ExcelError('#VALUE!', 'start_date %s must be positive' % str(serial_number))

y1, m1, d1 = date_from_int(serial_number)

return m1


def count(*args): # Excel reference: https://support.office.com/en-us/article/COUNT-function-a59cd7fc-b623-4d93-87a4-d23bf411294c
l = list(args)

Expand Down Expand Up @@ -590,10 +637,10 @@ def date(year, month, day): # Excel reference: https://support.office.com/en-us/

year, month, day = normalize_year(year, month, day) # taking into account negative month and day values

date_0 = datetime(1900, 1, 1)
date = datetime(year, month, day)
date_0 = datetime.datetime(1900, 1, 1)
date = datetime.datetime(year, month, day)

result = (datetime(year, month, day) - date_0).days + 2
result = (datetime.datetime(year, month, day) - date_0).days + 2

if result <= 0:
return ExcelError('#VALUE!', 'Date result is negative')
Expand Down Expand Up @@ -980,7 +1027,7 @@ def sqrt(number):

# https://support.office.com/en-ie/article/today-function-5eb3078d-a82c-4736-8930-2f51a028fdd9
def today():
reference_date = datetime.today().date()
reference_date = datetime.datetime.today().date()
days_since_epoch = reference_date - EXCEL_EPOCH
# why +2 ?
# 1 based from 1900-01-01
Expand Down
11 changes: 9 additions & 2 deletions koala/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import collections
import numbers
import re
import datetime as dt
from six import string_types

from openpyxl.compat import unicode
Expand Down Expand Up @@ -131,7 +132,7 @@ def resolve_range(rng, should_flatten = False, sheet=''):
start_row = start
end_col = "XFD"
end_row = end
else:
else:
sh, start_col, start_row = split_address(start)
sh, end_col, end_row = split_address(end)

Expand Down Expand Up @@ -407,11 +408,17 @@ def date_from_int(nb):
if nb > max_days:
nb -= max_days
else:
current_day = nb
current_day = int(nb)
nb = 0

return (current_year, current_month, current_day)

def int_from_date(date):
temp = dt.date(1899, 12, 30) # Note, not 31st Dec but 30th!
delta = date - temp

return float(delta.days) + (float(delta.seconds) / 86400)

def criteria_parser(criteria):

if is_number(criteria):
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ numpy==1.14.2
Cython==0.28.2
lxml==4.1.1
six==1.11.0
python-dateutil==2.8.0
33 changes: 31 additions & 2 deletions tests/excel/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,11 +719,14 @@ def test_first_argument_validity(self):
def test_positive_integers(self):
self.assertEqual(sqrt(16), 4)

def test_float(self):
self.assertEqual(sqrt(.25), .5)


class Test_Today(unittest.TestCase):

EXCEL_EPOCH = datetime.strptime("1900-01-01", '%Y-%m-%d').date()
reference_date = datetime.today().date()
EXCEL_EPOCH = datetime.datetime.strptime("1900-01-01", '%Y-%m-%d').date()
reference_date = datetime.datetime.today().date()
days_since_epoch = reference_date - EXCEL_EPOCH
todays_ordinal = days_since_epoch.days + 2

Expand All @@ -740,3 +743,29 @@ def test_first_argument_validity(self):

def test_concatenate(self):
self.assertEqual(concatenate("Hello", " ", "World!"), "Hello World!")


class Test_Year(unittest.TestCase):

def test_results(self):
self.assertEqual(year(43566), 2019) # 11/04/2019
self.assertEqual(year(43831), 2020) # 01/01/2020
self.assertEqual(year(36525), 1999) # 31/12/1999


class Test_Month(unittest.TestCase):

def test_results(self):
self.assertEqual(month(43566), 4) # 11/04/2019
self.assertEqual(month(43831), 1) # 01/01/2020
self.assertEqual(month(36525), 12) # 31/12/1999


class Test_Eomonth(unittest.TestCase):

def test_results(self):
self.assertEqual(eomonth(43566, 2), 43646) # 11/04/2019, add 2 months
self.assertEqual(eomonth(43831, 5), 44012) # 01/01/2020, add 5 months
self.assertEqual(eomonth(36525, 1), 36556) # 31/12/1999, add 1 month
self.assertEqual(eomonth(36525, 15), 36981) # 31/12/1999, add 15 month
self.assertNotEqual(eomonth(36525, 15), 36980) # 31/12/1999, add 15 month