Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Added type annotations for public API and `py.typed` marker (PEP 561)
* Added `message` to `JsonSchemaValuesException` joining messages of all collected errors
* Fixed the `date-time` format accepting months, days and hours out of range (such as `2020-19-39T29:00:00Z`) and rejecting a leap second (`23:59:60Z`)
* Fixed the `date` and `date-time` formats accepting days that do not exist in their month, such as `2021-02-29` and `2021-04-31`
* Fixed the `time` format accepting hours, minutes, seconds and offsets out of range (such as `29:99:99`)

=== 2.22.2 (2026-08-15)
Expand Down
9 changes: 8 additions & 1 deletion fastjsonschema/draft04.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
DOLLAR_FINDER = re.compile(r"(?<!\\)\$") # Finds any un-escaped $ (including inside []-sets)


# Calendar date: the day must exist in its month, and 29 February only in a leap year.
DATE_REGEX = (
r'\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|02-(?:0[1-9]|1\d|2[0-8]))'
r'|(?:\d\d(?:0[48]|[2468][048]|[13579][26])|(?:[02468][048]|[13579][26])00)-02-29'
)


# pylint: disable=too-many-instance-attributes,too-many-public-methods
class CodeGeneratorDraft04(CodeGenerator):
# pylint: disable=line-too-long
Expand All @@ -26,7 +33,7 @@ class CodeGeneratorDraft04(CodeGenerator):
# vs. 9 ms with a regex! Other modules are also ineffective or not available in standard
# library. Some regexps are not 100% precise but good enough, fast and without dependencies.
FORMAT_REGEXS = {
'date-time': r'^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])(t|T)(?:[01]\d|2[0-3]):[0-5]\d:(?:[0-5]\d|60)(?:\.\d+)?(?:[+-](?:[01]\d|2[0-3]):[0-5]\d|[+-](?:[01]\d|2[0-3])[0-5]\d|z|Z)\Z',
'date-time': r'^(?:' + DATE_REGEX + r')(t|T)(?:[01]\d|2[0-3]):[0-5]\d:(?:[0-5]\d|60)(?:\.\d+)?(?:[+-](?:[01]\d|2[0-3]):[0-5]\d|[+-](?:[01]\d|2[0-3])[0-5]\d|z|Z)\Z',
'email': r'^(?!.*\.\..*@)[^@.][^@]*(?<!\.)@[^@]+\.[^@]+\Z',
'hostname': r'^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9])\Z',
'ipv4': r'^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\Z',
Expand Down
3 changes: 2 additions & 1 deletion fastjsonschema/draft07.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from .draft04 import DATE_REGEX
from .draft06 import CodeGeneratorDraft06
from .generator import VALIDATION_EXCEPTIONS


class CodeGeneratorDraft07(CodeGeneratorDraft06):
FORMAT_REGEXS = dict(CodeGeneratorDraft06.FORMAT_REGEXS, **{
'date': r'^(?P<year>\d{4})-(?P<month>(0[1-9]|1[0-2]))-(?P<day>(0[1-9]|[12]\d|3[01]))\Z',
'date': r'^(?:' + DATE_REGEX + r')\Z',
'iri': r'^\w+:(\/?\/?)[^\s]+\Z',
'iri-reference': r'^(\w+:(\/?\/?))?[^#\\\s]*(#[^\\\s]*)?\Z',
'idn-email': r'^[^@]+@[^@]+\.[^@]+\Z',
Expand Down
19 changes: 19 additions & 0 deletions tests/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
('2020-01-01T01:01:61Z', exc),
('2020-01-01T01:01:01+24:00', exc),
('2020-01-01T01:01:01+01:60', exc),
('2020-02-30T01:01:01Z', exc),
('2021-02-29T01:01:01Z', exc),
('2021-04-31T01:01:01Z', exc),
('2020-02-29T01:01:01Z', '2020-02-29T01:01:01Z'),
('2021-04-30T01:01:01Z', '2021-04-30T01:01:01Z'),
])
def test_datetime(asserter, value, expected):
asserter({'type': 'string', 'format': 'date-time'}, value, expected)
Expand Down Expand Up @@ -81,6 +86,20 @@ def test_time(asserter, value, expected):
('2018-2-5', exc),
('2018-02-05', '2018-02-05'),
('2018-10-31', '2018-10-31'),
('2018-02-29', exc),
('2018-02-30', exc),
('2018-04-31', exc),
('2018-06-31', exc),
('2018-09-31', exc),
('2018-11-31', exc),
('1900-02-29', exc),
('2100-02-29', exc),
('2020-02-29', '2020-02-29'),
('2000-02-29', '2000-02-29'),
('2400-02-29', '2400-02-29'),
('2018-02-28', '2018-02-28'),
('2018-04-30', '2018-04-30'),
('2018-12-31', '2018-12-31'),
])
def test_date(asserter, value, expected):
asserter({
Expand Down
Loading