Skip to content

Commit

Permalink
Add 'now' variable to Snowfakery (#485)
Browse files Browse the repository at this point in the history
* Add 'now' variable to Snowfakery
  • Loading branch information
Paul Prescod authored Feb 23, 2022
1 parent 7d431d6 commit 99dc642
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 5 deletions.
85 changes: 83 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -983,7 +983,40 @@ Contact(id=3, FirstName=Gene, LastName=Wall, DepartmentCode=42Q3XX3N)

#### `today`

The `today` variable returns a date representing the current date. This date does not change chanage during the execution of a single recipe.
The `today` variable returns a date
representing the current date. This date
will not chanage during the execution of
a single recipe. See the `date` function
to learn more about this type of object.

#### `now`

The `now` variable returns a [datetime](#datetime)
representing the current moment. It outputs
microsecond precision and is in the UTC timezone.

Using this recipe, you can see three different
ways of outputting the timestamp:

```yaml
# tests/test_now.yml
- object: Times
fields:
current_datetime: ${{now}}
current_datetime_as_number: ${{now.timestamp()}}
current_datetime_without_microseconds: ${{int(now.timestamp())}}
```

This would generate field values similar to these:

```yaml
current_datetime=2022-02-23 15:39:49.513086+00:00, current_datetime_as_number=1645630789.513975, current_datetime_without_microseconds=1645630789
```

Experimentally, this variable seems to return a unique value
every time it is called, but it might depend on
your operating system and hardware setup
(e.g. a very fast CPU with a very slow system clock).

#### `fake:` and `fake.`

Expand Down Expand Up @@ -1013,12 +1046,60 @@ the_date: ${{date("2018-10-30")}}
another_date: ${{date(year=2018, month=11, day=30)}}
```

These objects have the following attributes:

- date.year: the year
- date.month: between 1 and 12 inclusive.
- date.day: between 1 and the number of days in the given month of the given year.

And these methods:

- date.weekday(): the day of the week as an integer, where Monday is 0 and Sunday is 6. For example, date(2002, 12, 4).weekday() == 2, a Wednesday.
- date.isoformat() - string representing the date in ISO 8601 format, YYYY-MM-DD
- strftime(format) - create a string representing the time under the control of an explicit [format string.](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes)

#### `datetime`

The `datetime` function can generate
a new datetime object from year/month/day parts:

```yaml
# tests/test_datetime.yml
- object: Datetimes
fields:
from_date: ${{datetime(year=2000, month=1, day=1)}}
from_datetime: ${{datetime(year=2000, month=1, day=1, hour=1, minute=1, second=1)}}
right_now: ${{now}}
hour: ${{now.hour}}
minute: ${{now.minute}}
second: ${{now.second}}
right_now_with_timezone: ${{now.astimezone()}}
to_date: ${{now.date()}}
```

These objects have the following attributes:

- datetime.year: the year
- datetime.month: between 1 and 12 inclusive.
- datetime.day: between 1 and the number of days in the given month of the given year.
- datetime.hour: the hour
- datetime.minute: the minute
- datetime.second: the second

And these methods:

- date.weekday(): the day of the week as an integer, where Monday is 0 and Sunday is 6. For example, date(2002, 12, 4).weekday() == 2, a Wednesday.
- date.isoformat() - string representing the date in ISO 8601 format, YYYY-MM-DD
- strftime(format) - create a string representing the time under the control of an explicit [format string.](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes)
- astimezone() - create a timezone-aware datetime for your current timezone
- date() - convert a datetime into a date.

#### `relativedelta`

The [`relativedelta` function](https://dateutil.readthedocs.io/en/stable/relativedelta.html) from `dateutil` is available for use in calculations. For example:

```yaml
${{ date(Date_Established__c) + relativedelta(months=child_index) }}
${{ date(Date_Established__c) + relativedelta(months=) }}
```

Some plugins are also interested in a `template` variable that has an `id` attribute that represents a unique identifier for the current template. Look at
Expand Down
3 changes: 2 additions & 1 deletion snowfakery/data_generator_runtime.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from collections import defaultdict, ChainMap
from datetime import date
from datetime import date, datetime, timezone
from contextlib import contextmanager

from typing import Optional, Dict, Sequence, Mapping, NamedTuple, Set
Expand Down Expand Up @@ -586,6 +586,7 @@ def simple_field_vars(self):
"child_index": obj._child_index if obj else None,
"this": obj,
"today": interpreter.globals.today,
"now": datetime.now(timezone.utc),
"fake": self.runtime_context.faker_template_library,
"template": self.runtime_context.current_template,
**interpreter.options,
Expand Down
10 changes: 10 additions & 0 deletions tests/test_datetime.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
- object: Datetimes
fields:
from_date: ${{datetime(year=2000, month=1, day=1)}}
from_datetime: ${{datetime(year=2000, month=1, day=1, hour=1, minute=1, second=1)}}
right_now: ${{now}}
hour: ${{now.hour}}
minute: ${{now.minute}}
second: ${{now.second}}
right_now_with_timezone: ${{now.astimezone()}}
to_date: ${{now.date()}}
5 changes: 5 additions & 0 deletions tests/test_now.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- object: Times
fields:
current_datetime: ${{now}}
current_datetime_as_number: ${{now.timestamp()}}
current_datetime_without_microseconds: ${{int(now.timestamp())}}
31 changes: 29 additions & 2 deletions tests/test_template_funcs.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from io import StringIO
from unittest import mock
import pydantic
import datetime

from datetime import datetime, date

from snowfakery.data_generator import generate
from snowfakery.data_gen_exceptions import DataGenError
Expand Down Expand Up @@ -244,6 +244,33 @@ def test_date_from_datetime(self, write_row):
generate(StringIO(yaml), {}, None)
assert write_row.mock_calls[0][1][1]["a"] == "2012-01-01"

def test_now_variable(self, generated_rows):
yaml = """
- object : A
fields:
a: ${{now}}
b: ${{now}}
"""
generate(StringIO(yaml), {}, None)
assert datetime.fromisoformat(generated_rows.table_values("A", 0, "a"))
assert datetime.fromisoformat(generated_rows.table_values("A", 0, "b"))
assert generated_rows.table_values("A", 0, "a") != generated_rows.table_values(
"A", 0, "b"
)

@mock.patch("snowfakery.data_generator_runtime.datetime")
def test_now_calls_datetime_now(self, datetime):
now = datetime.now = mock.Mock()
yaml = """
- object : A
fields:
a: ${{now}}
b: ${{now}}
c: ${{now}}
"""
generate(StringIO(yaml))
assert len(now.mock_calls) == 3

@mock.patch(write_row_path)
def test_old_syntax(self, write_row):
yaml = """
Expand Down Expand Up @@ -417,7 +444,7 @@ def test_random_choice_nonstring_keys(self, generated_rows):
class ResultModel(pydantic.BaseModel):
id: int
bool: bool
date: datetime.date
date: date

assert ResultModel(**result)

Expand Down

0 comments on commit 99dc642

Please sign in to comment.