Skip to content

Commit

Permalink
Merge pull request #112 from igorkramaric/add-current-date-functions
Browse files Browse the repository at this point in the history
Add current date functions
  • Loading branch information
igorkramaric authored Apr 4, 2023
2 parents 7402703 + f298f75 commit 47ff96f
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 29 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,13 @@ Arrays:
- `"x" in ("red", "green", "blue")`

### Functions

timestamp
- `timestamp(YYYY-MM-DD)` or `timestamp(YYYY-MM-DD HH:MM)` this is a helper function which generates the unix timestamp corresponding to the date (and optionally, time) entered. When the daffodil is evaluated the datetime is functionally a number, but this lets you write the daffodil in a way that is easier to read and understand. If hours and minutes are entered they are interpreted as 24 hour time UTC.
- `timestamp([CURRENT_RANGE])` where `CURRENT_RANGE` option may be one of the following
- CURRENT_DAY
- CURRENT_WEEK (week starting with Monday)
- CURRENT_MONTH
- CURRENT_YEAR

Examples:
- people who began `mystudy` after halloween 2017:
Expand All @@ -211,7 +216,17 @@ Examples:
balloonstudy__started >= timestamp(2017-11-23 2:00)
balloonstudy__started < timestamp(2017-11-23 17:00)
```
- people who participated in `balloonstudy` this month:

```
balloonstudy__started >= timestamp(CURRENT_MONTH)
```
- people who participated in `balloonstudy` this week except today:

```
balloonstudy__started >= timestamp(CURRENT_WEEK)
balloonstudy__started < timestamp(CURRENT_DAY)
```
### Comparison operators

Operator | Example | Meaning
Expand Down
32 changes: 30 additions & 2 deletions daffodil/parser.pyx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import string
from datetime import datetime, timezone
from datetime import datetime, timezone, timedelta
from .exceptions import ParseError
from .predicate cimport DictionaryPredicateDelegate
from .simulation_delegate cimport SimulationMatchingDelegate
Expand Down Expand Up @@ -27,9 +27,22 @@ OPERATORS = (

MAX_OP_LENGTH = max(len(op) for op in OPERATORS)

CURRENT_YEAR = "CURRENT_YEAR"
CURRENT_MONTH = "CURRENT_MONTH"
CURRENT_WEEK = "CURRENT_WEEK"
CURRENT_DAY = "CURRENT_DAY"

TS_CONSTANTS = (
CURRENT_YEAR,
CURRENT_MONTH,
CURRENT_WEEK,
CURRENT_DAY
)


DEF TS_FORMATS = (
"%Y-%m-%d %H:%M",
"%Y-%m-%d",
"%Y-%m-%d"
)

cdef class Token:
Expand All @@ -42,8 +55,13 @@ cdef class Token:

cdef class TimeStamp(Token):
def __cinit__(self, str content):
content = content.strip()
self.raw_content = content

if content in TS_CONSTANTS:
self.content = self.date_func_to_timestamp(content)
return

for ts_fmt in TS_FORMATS:
try:
dt = datetime.strptime(content, ts_fmt).replace(tzinfo=timezone.utc)
Expand All @@ -54,6 +72,16 @@ cdef class TimeStamp(Token):
else:
raise ParseError(f'"timestamp({content})" couldn\'t be parsed')

def date_func_to_timestamp(self, time_unit):
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)

return int({
CURRENT_YEAR: today.replace(month=1, day=1),
CURRENT_MONTH: today.replace(day=1),
CURRENT_WEEK: today - timedelta(days=today.weekday()),
CURRENT_DAY: today,
}[time_unit].replace(tzinfo=timezone.utc).timestamp())


cdef class GroupStart(Token):
def is_end(self, token):
Expand Down
Empty file added test/data/__init__.py
Empty file.
9 changes: 0 additions & 9 deletions test/data/nyc_sat_scores.json

This file was deleted.

22 changes: 22 additions & 0 deletions test/data/nyc_sat_scores.py

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions test/data/test_data_generation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from datetime import datetime, timezone, timedelta
from random import randint


now = datetime.now().replace(tzinfo=timezone.utc)
day_start = now.replace(hour=0, minute=0, second=0, microsecond=0).replace(tzinfo=timezone.utc)


def random_until_now(start):
return randint(int(start.timestamp()), int(now.timestamp()))


def days_ago_ts(days):
return (now - timedelta(days=days)).timestamp()


days_ago_0 = random_until_now(day_start)
days_ago_3 = days_ago_ts(3)

weeks_ago_0 = random_until_now(
(day_start - timedelta(days=day_start.weekday()))
)
weeks_ago_5 = days_ago_ts(7 * 5)

months_ago_0 = random_until_now(day_start.replace(day=1))
months_ago_7 = days_ago_ts(7 * 30)

years_ago_0 = random_until_now(day_start.replace(month=1, day=1))
years_ago_2 = days_ago_ts(2 * 365)
60 changes: 43 additions & 17 deletions test/tests.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from builtins import zip
import sys
import os
import json
import unittest
import re
import itertools

from data.nyc_sat_scores import NYC_SAT_SCORES

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

Expand All @@ -17,22 +16,9 @@
from daffodil.exceptions import ParseError


def load_test_data(dataset):
filename = os.path.join(os.path.dirname(__file__), 'data', '{0}.json'.format(dataset))
with open(filename, 'r') as f:
data = json.load(f)
return data


def load_nyc_opendata(dataset):
dataset = load_test_data(dataset)
columns = [c['fieldName'] for c in dataset['meta']['view']['columns']]
d = [dict(list(zip(columns, row_values))) for row_values in dataset['data']]


class BaseTest(unittest.TestCase):
def setUp(self):
self.d = load_test_data('nyc_sat_scores')
self.d = NYC_SAT_SCORES

def filter(self, daff_src):
return Daffodil(daff_src)(self.d)
Expand Down Expand Up @@ -567,6 +553,46 @@ def test_timestamp_notin(self):
)
""")

def test_timestamp_current_day(self):
self.assert_filter_has_n_results(4, """
_ack1 > timestamp(CURRENT_DAY)
""")
self.assert_filter_has_n_results(1, """
_ack1 < timestamp(CURRENT_DAY)
""")

def test_timestamp_current_week(self):
self.assert_filter_has_n_results(3, """
_ack2 > timestamp(CURRENT_WEEK)
""")
self.assert_filter_has_n_results(2, """
_ack2 < timestamp(CURRENT_WEEK)
""")

def test_timestamp_current_month(self):
self.assert_filter_has_n_results(2, """
_ack3 >= timestamp(CURRENT_MONTH)
""")
self.assert_filter_has_n_results(3, """
_ack3 < timestamp(CURRENT_MONTH)
""")

def test_timestamp_current_year(self):
self.assert_filter_has_n_results(1, """
_ack4 > timestamp(CURRENT_YEAR)
""")
self.assert_filter_has_n_results(4, """
_ack4 <= timestamp(CURRENT_YEAR)
""")

def test_timestamp_current_date_mix(self):
self.assert_filter_has_n_results(2, """
_ack1 >= timestamp(CURRENT_DAY )
_ack2 > timestamp( CURRENT_WEEK)
_ack3 ?= true
_ack4 < timestamp( CURRENT_YEAR )
""")

def test_or_nonexistence(self):
self.assert_filter_has_n_results(4, """
num_of_sat_test_takers = 50
Expand Down Expand Up @@ -1697,7 +1723,7 @@ def test_none(self):
management.call_command("migrate")

BasicHStoreData.objects.all().delete()
for record in load_test_data('nyc_sat_scores'):
for record in NYC_SAT_SCORES:
BasicHStoreData.objects.create(hsdata=record)

unittest.main()
Expand Down

0 comments on commit 47ff96f

Please sign in to comment.