From d59c51651c6a17024e3a7369182c537dc6ff3c40 Mon Sep 17 00:00:00 2001 From: JingsongLi Date: Tue, 13 Oct 2026 20:53:26 +0800 Subject: [PATCH] [python] Use Native for Java calendar query authorization masks --- .../pypaimon/common/predicate_json_parser.py | 104 ++++++++++-- .../tests/native_calendar_mask_test.py | 154 ++++++++++++++++++ .../tests/predicate_json_parser_test.py | 65 ++++++++ .../resources/calendar_extract_java.jsonl | 146 +++++++++++++++++ 4 files changed, 452 insertions(+), 17 deletions(-) create mode 100644 paimon-python/pypaimon/tests/native_calendar_mask_test.py create mode 100644 paimon-python/pypaimon/tests/resources/calendar_extract_java.jsonl diff --git a/paimon-python/pypaimon/common/predicate_json_parser.py b/paimon-python/pypaimon/common/predicate_json_parser.py index df14ce209731..bfce29d4cc12 100644 --- a/paimon-python/pypaimon/common/predicate_json_parser.py +++ b/paimon-python/pypaimon/common/predicate_json_parser.py @@ -54,13 +54,8 @@ def _wrap_int32(value: int) -> int: "TRAILING": (pc.utf8_rtrim, str.rstrip), } -# Calendar-field extractions on a DATE / TIMESTAMP field, mirroring Java's -# DateExtractTransform subclasses (name -> the Arrow kernel over a timestamp). -# QUARTER matches (month - 1) / 3 + 1; the day-of-week variants match Java's -# java.time numbering: WEEKDAY is Monday=0..Sunday=6, ISO_DAY_OF_WEEK is -# Monday=1..Sunday=7, DAY_OF_WEEK is Sunday=1..Saturday=7. WEEK / YEAR_OF_WEEK -# are the ISO week-of-week-based-year and the ISO week-based year. The rest map -# one to one. +# Java DateExtractTransform names. LTZ is excluded because a calendar field +# would depend on a session time zone, which authorization rules do not carry. _DATE_EXTRACT = { "YEAR": pc.year, "MONTH": pc.month, @@ -199,16 +194,93 @@ def _apply_predicate_transform(transform: dict, batch: pa.RecordBatch, def _date_extract(name: str, field_ref: dict, batch: pa.RecordBatch) -> pa.Array: - """Extract a calendar field from a DATE / TIMESTAMP column, as INT. + """Java calendar extraction on DATE days or Timestamp milliseconds. - A DATE is read at the start of its day (Java's LocalDate.atStartOfDay), - so it is cast to a timestamp first and HOUR/MINUTE/SECOND come out as 0. - Nulls propagate. Mirrors Java DateExtractTransform. + Timestamp.toLocalDateTime narrows the epoch day to a signed int. Work on + that representation directly, including DATE32 values which cannot be + cast to an Arrow microsecond timestamp or a Python datetime. """ + from pypaimon.casting.temporal_utils import _signed + stored = field_ref.get('type') + if not isinstance(stored, str): + raise ValueError("Calendar transform field needs a DATE or TIMESTAMP type") + # Validate the complete stored SQL spelling before schema-name rebinding. + match = re.fullmatch(r"\s*(DATE(?=\s|\Z)|TIMESTAMP(?=\s|\(|\Z)(?:\s*\(\s*([0-9]+)\s*\))?" + r"(?:\s*WITHOUT\s+TIME\s+ZONE(?=\s|\Z))?)" + r"(?:\s*(?:NOT\s+)?NULL)?\s*", stored, flags=re.IGNORECASE) + if match is None or (match.group(2) is not None and int(match.group(2)) > 9): + raise ValueError("Calendar transforms require DATE or TIMESTAMP without time zone") column = _field_column(field_ref, batch) - if pa.types.is_date(column.type): - column = pc.cast(column, pa.timestamp("us")) - return pc.cast(_DATE_EXTRACT[name](column), pa.int32()) + date = pa.types.is_date32(column.type) + timestamp = pa.types.is_timestamp(column.type) and column.type.tz is None + if not date and not timestamp: + raise ValueError("Calendar transforms require DATE or TIMESTAMP without time zone") + raw = column.cast(pa.int32() if date else pa.int64()) + unit = column.type.unit if timestamp else None + # Arrow calendar kernels agree with Java within years 1..9999. Keep their + # vectorized execution for ordinary batches; outside this range use Java's + # epoch-day arithmetic, avoiding Arrow's bounded calendar representation. + bounds = pc.min_max(raw).as_py() + ticks_per_day = 1 if date else 86400 * {'s': 1, 'ms': 1000, 'us': 1000000, 'ns': 1000000000}[unit] + if (bounds['min'] is None or + (-719162 * ticks_per_day <= bounds['min'] and + bounds['max'] < 2932897 * ticks_per_day)): + source = column.cast(pa.timestamp('us')) if date else column + return _DATE_EXTRACT[name](source).cast(pa.int32()) + values = raw.to_pylist() + results = [] + for value in values: + if value is None: + results.append(None) + continue + if date: + day, millis = value, 0 + else: + if unit == 's': + millis = _signed(value * 1000, 64) + elif unit == 'ms': + millis = value + else: + millis = value // (1000 if unit == 'us' else 1000000) + day, millis = _signed(millis // 86400000, 32), millis % 86400000 + results.append(_calendar_field(name, day, millis)) + return pa.array(results, type=pa.int32()) + + +def _calendar_field(name, day, millis): + from pypaimon.casting.temporal_utils import _epoch_day_to_date + year, month, date = _epoch_day_to_date(day) + weekday = (day + 3) % 7 + if name == 'YEAR': + return year + if name == 'MONTH': + return month + if name == 'DAY': + return date + if name == 'HOUR': + return millis // 3600000 + if name == 'MINUTE': + return millis // 60000 % 60 + if name == 'SECOND': + return millis // 1000 % 60 + if name == 'QUARTER': + return (month - 1) // 3 + 1 + if name == 'DAY_OF_YEAR': + return day - _january_first(year) + 1 + if name == 'WEEKDAY': + return weekday + if name == 'ISO_DAY_OF_WEEK': + return weekday + 1 + if name == 'DAY_OF_WEEK': + return (weekday + 1) % 7 + 1 + thursday = day + 3 - weekday + week_year = _epoch_day_to_date(thursday)[0] + return week_year if name == 'YEAR_OF_WEEK' else (thursday - _january_first(week_year)) // 7 + 1 + + +def _january_first(year): + era, yoe = divmod(year - 1, 400) + return era * 146097 + yoe * 365 + yoe // 4 - yoe // 100 + 306 - 719468 def _substring(inputs, batch: pa.RecordBatch) -> pa.Array: @@ -799,9 +871,7 @@ def _collect_all_field_refs_from_transform(transform: dict, fields: set = None) if fields is None: fields = set() name = transform.get("name") - if name == "FIELD_REF" and "fieldRef" in transform: - fields.add(transform["fieldRef"]["name"]) - elif name == "CAST" and "fieldRef" in transform: + if (name in ("FIELD_REF", "CAST") or name in _DATE_EXTRACT) and "fieldRef" in transform: fields.add(transform["fieldRef"]["name"]) else: for inp in transform.get("inputs", []): diff --git a/paimon-python/pypaimon/tests/native_calendar_mask_test.py b/paimon-python/pypaimon/tests/native_calendar_mask_test.py new file mode 100644 index 000000000000..e1d2cd6ceb19 --- /dev/null +++ b/paimon-python/pypaimon/tests/native_calendar_mask_test.py @@ -0,0 +1,154 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Existing calendar authorization masks through Native and Python readers.""" + +import json +from unittest.mock import patch + +import pyarrow as pa +import pytest + +from pypaimon import Schema +from pypaimon.api.api_response import AuthTableQueryResponse +from pypaimon.common.predicate_builder import PredicateBuilder +from pypaimon.read.native_plan import native_runtime_available +from pypaimon.read.split_serializer import deserialize_split_v1, serialize_split_v1 +from pypaimon.schema.schema_change import SchemaChange +from pypaimon.tests import native_query_auth_test + +rest_catalog = native_query_auth_test.rest_catalog +_read = native_query_auth_test._read + +pytestmark = [pytest.mark.native_plan, pytest.mark.skipif( + not native_runtime_available(), reason='Rust reader required')] +_PARTS = ['YEAR', 'MONTH', 'DAY', 'HOUR', 'MINUTE', 'SECOND', 'QUARTER', 'DAY_OF_YEAR', + 'WEEKDAY', 'ISO_DAY_OF_WEEK', 'DAY_OF_WEEK', 'WEEK', 'YEAR_OF_WEEK'] + + +@pytest.fixture(params=[ + ('append', {}), ('primary-key', {'bucket': '1'}), + ('data-evolution', {'row-tracking.enabled': 'true', 'data-evolution.enabled': 'true'}), +], ids=lambda mode: mode[0]) +def calendar_table(rest_catalog, request): + catalog, server = rest_catalog + mode, options = request.param + schema = pa.schema([('id', pa.int32()), ('n', pa.int32()), ('d', pa.date32()), + ('ts', pa.timestamp('ms')), ('us', pa.timestamp('us')), ('ns', pa.timestamp('ns'))]) + catalog.create_table('default.calendar', Schema.from_pyarrow_schema( + schema, primary_keys=['id'] if mode == 'primary-key' else None, options=options), False) + table = catalog.get_table('default.calendar') + builder = table.new_batch_write_builder() + writer, commit = builder.new_write(), builder.new_commit() + try: + # Leap day, ISO year boundary, pre-epoch fractional second, NULL. + days = [19782, 20087, -1, None] + millis = [1709251199123, 1735516800000, -1, None] + writer.write_arrow(pa.table({ + 'id': [1, 2, 3, 4], 'n': [0] * 4, 'd': days, 'ts': millis, + 'us': [value * 1000 + 456 if value is not None else None for value in millis], + 'ns': [value * 1000000 + 456789 if value is not None else None for value in millis], + }, schema=schema)) + commit.commit(writer.prepare_commit()) + finally: + writer.close() + commit.close() + catalog.alter_table(table.identifier, [SchemaChange.set_option('query-auth.enabled', 'true')]) + return catalog.get_table('default.calendar'), server + + +def _rule(part, field, type_): + return json.dumps({'name': part, 'fieldRef': {'name': field, 'index': 99, 'type': type_}}) + + +@pytest.mark.parametrize('field,type_', [('d', 'DATE'), ('ts', 'TIMESTAMP(3)'), + ('us', 'TIMESTAMP(6)'), ('ns', 'TIMESTAMP(9)')]) +def test_all_calendar_masks_read_hidden_operands_natively(calendar_table, field, type_): + table, server = calendar_table + # Expected Java values for DATE midnight and TIMESTAMP's wall clock. + expected = [ + [2024, 2024, 1969, None], [2, 12, 12, None], [29, 30, 31, None], + [0, 0, 0, None], [0, 0, 0, None], [0, 0, 0, None], [1, 4, 4, None], + [60, 365, 365, None], [3, 0, 2, None], [4, 1, 3, None], [5, 2, 4, None], + [9, 1, 1, None], [2024, 2025, 1970, None], + ] + if field != 'd': + expected[3:6] = [[23, 0, 23, None], [59, 0, 59, None], [59, 0, 59, None]] + for part, values in zip(_PARTS, expected): + server.table_query_auth_store[table.identifier.get_full_name()] = AuthTableQueryResponse( + column_masking={'n': _rule(part, field, type_)}) + for native in [False, True]: + result = _read(table, native, ['id', 'n']).sort_by([('id', 'ascending')]) + assert result.column('n').type == pa.int32() + assert result.column('n').to_pylist() == values, (part, field, native) + + +@pytest.mark.parametrize('mode', ['wire', 'setup-failure', 'disable-native']) +def test_calendar_fallback_keeps_hidden_dependency_and_captured_rule(calendar_table, mode): + table, server = calendar_table + server.table_query_auth_store[table.identifier.get_full_name()] = AuthTableQueryResponse( + column_masking={'n': _rule('YEAR_OF_WEEK', 'd', 'DATE')}) + table = table.copy({'read.native.enabled': 'true', 'scan.native-plan.enabled': 'true'}) + builder = (table.new_read_builder().with_projection(['id']).with_limit(1) + .with_filter(PredicateBuilder(table.fields).equal('n', 2025))) + scan = builder.new_scan() + with patch.object(scan.file_scanner, 'scan', side_effect=AssertionError('Native plan fell back')): + splits = scan.plan().splits() + server.table_query_auth_store[table.identifier.get_full_name()] = AuthTableQueryResponse() + if mode == 'wire': + splits = [deserialize_split_v1(serialize_split_v1(split), []) for split in splits] + if mode == 'disable-native': + builder = (table.copy({'read.native.enabled': 'false'}).new_read_builder().with_projection(['id']) + .with_filter(PredicateBuilder(table.fields).equal('n', 2025)).with_limit(1)) + if mode == 'setup-failure': + with patch('pypaimon.read.native_plan.native_read', side_effect=RuntimeError('setup failed')): + rows = builder.new_read().to_arrow(splits).to_pylist() + else: + rows = builder.new_read().to_arrow(splits).to_pylist() + assert rows == [{'id': 2}] + + +@pytest.mark.parametrize('native', [False, True]) +def test_calendar_mask_cannot_expose_another_masked_operand(calendar_table, native): + table, server = calendar_table + server.table_query_auth_store[table.identifier.get_full_name()] = AuthTableQueryResponse( + column_masking={'n': _rule('YEAR', 'd', 'DATE'), 'd': json.dumps({'name': 'NULL'})}) + table = table.copy({'scan.native-plan.enabled': str(native).lower()}) + with pytest.raises(ValueError, match="masked too"): + table.new_read_builder().with_projection(['n']).new_scan().plan() + + +@pytest.mark.parametrize('native', [False, True]) +def test_stale_calendar_operand_fails_closed(calendar_table, native): + table, server = calendar_table + server.table_query_auth_store[table.identifier.get_full_name()] = AuthTableQueryResponse( + column_masking={'n': _rule('YEAR', 'missing', 'DATE')}) + table = table.copy({'scan.native-plan.enabled': str(native).lower()}) + with pytest.raises(ValueError, match="missing"): + table.new_read_builder().with_projection(['n']).new_scan().plan() + + +@pytest.mark.parametrize('field,type_', [('d', 'TIMESTAMP(6)'), ('ts', 'DATE')]) +def test_calendar_mask_rebinds_stored_temporal_family(calendar_table, field, type_): + table, server = calendar_table + # Java validates the stored temporal family, then copies the FieldRef from + # the current schema. The actual column decides the executed calendar value. + server.table_query_auth_store[table.identifier.get_full_name()] = AuthTableQueryResponse( + column_masking={'n': _rule('YEAR_OF_WEEK', field, type_)}) + for native in [False, True]: + result = _read(table, native, ['n', 'id']).sort_by([('id', 'ascending')]) + assert result.column('n').to_pylist() == [2024, 2025, 1970, None] diff --git a/paimon-python/pypaimon/tests/predicate_json_parser_test.py b/paimon-python/pypaimon/tests/predicate_json_parser_test.py index 7008a40e4d07..06cae7efa890 100644 --- a/paimon-python/pypaimon/tests/predicate_json_parser_test.py +++ b/paimon-python/pypaimon/tests/predicate_json_parser_test.py @@ -19,6 +19,7 @@ import datetime import json import unittest +from pathlib import Path import pyarrow as pa @@ -861,6 +862,70 @@ def test_date_field_starts_at_midnight(self): self.assertEqual(self._apply("MONTH", batch, "d", "DATE"), [1, None]) self.assertEqual(self._apply("HOUR", batch, "d", "DATE"), [0, None]) + def test_calendar_parts_match_java_golden_for_every_storage_unit(self): + parts = ['YEAR', 'MONTH', 'DAY', 'HOUR', 'MINUTE', 'SECOND', 'QUARTER', 'DAY_OF_YEAR', + 'WEEKDAY', 'ISO_DAY_OF_WEEK', 'DAY_OF_WEEK', 'WEEK', 'YEAR_OF_WEEK'] + golden = Path(__file__).parent / 'resources' / 'calendar_extract_java.jsonl' + for line in golden.read_text().splitlines(): + case = json.loads(line) + unit = case['unit'] + type_ = pa.date32() if unit == 'date' else pa.timestamp(unit) + wire = 'DATE' if unit == 'date' else 'TIMESTAMP(9)' + batch = pa.RecordBatch.from_pydict({'t': pa.array([case['value'], None], type=type_)}) + for part, expected in zip(parts, case['parts']): + with self.subTest(unit=unit, value=case['value'], part=part): + self.assertEqual(self._apply(part, batch, 't', wire), [expected, None]) + + def test_empty_and_all_null_calendar_batches(self): + for unit in ['date', 's', 'ms', 'us', 'ns']: + type_ = pa.date32() if unit == 'date' else pa.timestamp(unit) + wire = 'DATE' if unit == 'date' else 'TIMESTAMP(9)' + for values in [[], [None, None]]: + batch = pa.RecordBatch.from_pydict({'t': pa.array(values, type=type_)}) + for part in ['YEAR', 'MONTH', 'DAY', 'HOUR', 'MINUTE', 'SECOND', 'QUARTER', 'DAY_OF_YEAR', + 'WEEKDAY', 'ISO_DAY_OF_WEEK', 'DAY_OF_WEEK', 'WEEK', 'YEAR_OF_WEEK']: + with self.subTest(unit=unit, values=values, part=part): + self.assertEqual(self._apply(part, batch, 't', wire), values) + + def test_calendar_operand_is_a_dependency_even_if_hidden(self): + for part in ['YEAR', 'MONTH', 'DAY', 'HOUR', 'MINUTE', 'SECOND', 'QUARTER', 'DAY_OF_YEAR', + 'WEEKDAY', 'ISO_DAY_OF_WEEK', 'DAY_OF_WEEK', 'WEEK', 'YEAR_OF_WEEK']: + rule = {'name': part, 'fieldRef': {'name': 'hidden', 'index': 99, 'type': 'DATE'}} + predicate = {'kind': 'LEAF', 'function': 'IS_NULL', 'transform': rule} + self.assertEqual(extract_referenced_fields(json.dumps(predicate)), {'hidden'}) + + def test_stored_and_rebound_calendar_types_are_validated(self): + for stored in ['INT', 'BIGINT', 'TIME(3)', 'TIMESTAMP_LTZ(3)', 'TIMESTAMP(10)', 'DATENULL', + 'TIMESTAMP(٣)', 'TIMESTAMP(3)', + 'TIMESTAMP(3) WITHOUT TIME ZONENULL', + 'TIMESTAMP(3) WITHOUT TIME ZONENOT NULL', + {'type': 'DATE'}, {'type': 'TIMESTAMP(6)'}, None]: + with self.subTest(stored=stored), self.assertRaises(ValueError): + self._apply('YEAR', self._ts_batch(), 't', stored) + for type_, value in [(pa.int32(), 1), (pa.time32('ms'), 1), (pa.timestamp('ms', tz='UTC'), 1)]: + batch = pa.RecordBatch.from_pydict({'t': pa.array([value], type=type_)}) + with self.subTest(type=type_), self.assertRaises(ValueError): + self._apply('YEAR', batch, 't', 'DATE') + # As in Java copyWithNewInputs, DATE and local TIMESTAMP may rebind to + # one another; the actual column decides the executed calendar value. + for stored in ['DATE', 'DATE NULL', 'TIMESTAMP(3)NOT NULL', 'TIMESTAMP WITHOUT TIME ZONE', + 'TIMESTAMP(3) WITHOUT TIME ZONE', + 'TIMESTAMP(3)WITHOUT TIME ZONE']: + with self.subTest(stored=stored): + self.assertEqual(self._apply('YEAR', self._ts_batch(), 't', stored), [2024, 2024, None]) + + def test_full_date32_domain_does_not_require_a_timestamp_cast(self): + batch = pa.RecordBatch.from_pydict({"d": pa.array([2147483647, -2147483648, None], type=pa.date32())}) + self.assertEqual(self._apply("HOUR", batch, "d", "DATE"), [0, 0, None]) + + def test_timestamp_epoch_day_narrows_as_java_int_before_calendar_extraction(self): + # Java Timestamp narrows its millisecond epoch day to a signed int. + ticks = (2147483648 * 86400000) + batch = pa.RecordBatch.from_pydict({"t": pa.array([ticks, -1, None], type=pa.timestamp("ms"))}) + days = pa.RecordBatch.from_pydict({"d": pa.array([-2147483648, -1, None], type=pa.date32())}) + self.assertEqual(self._apply("YEAR", batch, "t", "TIMESTAMP(3)"), + self._apply("YEAR", days, "d", "DATE")) + def test_wired_through_a_leaf_filter(self): pred = json.dumps({ "kind": "LEAF", diff --git a/paimon-python/pypaimon/tests/resources/calendar_extract_java.jsonl b/paimon-python/pypaimon/tests/resources/calendar_extract_java.jsonl new file mode 100644 index 000000000000..101de934cfeb --- /dev/null +++ b/paimon-python/pypaimon/tests/resources/calendar_extract_java.jsonl @@ -0,0 +1,146 @@ +{"unit":"date","value":0,"calendar":"1970-01-01","parts":[1970,1,1,0,0,0,1,1,3,4,5,1,1970]} +{"unit":"date","value":-1,"calendar":"1969-12-31","parts":[1969,12,31,0,0,0,4,365,2,3,4,1,1970]} +{"unit":"date","value":-719528,"calendar":"0000-01-01","parts":[0,1,1,0,0,0,1,1,5,6,7,52,-1]} +{"unit":"date","value":-719529,"calendar":"-0001-12-31","parts":[-1,12,31,0,0,0,4,365,4,5,6,52,-1]} +{"unit":"date","value":-719469,"calendar":"0000-02-29","parts":[0,2,29,0,0,0,1,60,1,2,3,9,0]} +{"unit":"date","value":-719103,"calendar":"0001-03-01","parts":[1,3,1,0,0,0,1,60,3,4,5,9,1]} +{"unit":"date","value":-2147483648,"calendar":"-5877641-06-23","parts":[-5877641,6,23,0,0,0,2,174,1,2,3,26,-5877641]} +{"unit":"date","value":-2147483647,"calendar":"-5877641-06-24","parts":[-5877641,6,24,0,0,0,2,175,2,3,4,26,-5877641]} +{"unit":"date","value":2147483646,"calendar":"+5881580-07-10","parts":[5881580,7,10,0,0,0,3,192,3,4,5,28,5881580]} +{"unit":"date","value":2147483647,"calendar":"+5881580-07-11","parts":[5881580,7,11,0,0,0,3,193,4,5,6,28,5881580]} +{"unit":"date","value":-25509,"calendar":"1900-02-28","parts":[1900,2,28,0,0,0,1,59,2,3,4,9,1900]} +{"unit":"date","value":-25508,"calendar":"1900-03-01","parts":[1900,3,1,0,0,0,1,60,3,4,5,9,1900]} +{"unit":"date","value":11015,"calendar":"2000-02-28","parts":[2000,2,28,0,0,0,1,59,0,1,2,9,2000]} +{"unit":"date","value":11016,"calendar":"2000-02-29","parts":[2000,2,29,0,0,0,1,60,1,2,3,9,2000]} +{"unit":"date","value":11017,"calendar":"2000-03-01","parts":[2000,3,1,0,0,0,1,61,2,3,4,9,2000]} +{"unit":"date","value":16800,"calendar":"2015-12-31","parts":[2015,12,31,0,0,0,4,365,3,4,5,53,2015]} +{"unit":"date","value":16801,"calendar":"2016-01-01","parts":[2016,1,1,0,0,0,1,1,4,5,6,53,2015]} +{"unit":"date","value":16803,"calendar":"2016-01-03","parts":[2016,1,3,0,0,0,1,3,6,7,1,53,2015]} +{"unit":"date","value":16804,"calendar":"2016-01-04","parts":[2016,1,4,0,0,0,1,4,0,1,2,1,2016]} +{"unit":"date","value":18259,"calendar":"2019-12-29","parts":[2019,12,29,0,0,0,4,363,6,7,1,52,2019]} +{"unit":"date","value":18260,"calendar":"2019-12-30","parts":[2019,12,30,0,0,0,4,364,0,1,2,1,2020]} +{"unit":"date","value":18627,"calendar":"2020-12-31","parts":[2020,12,31,0,0,0,4,366,3,4,5,53,2020]} +{"unit":"date","value":18628,"calendar":"2021-01-01","parts":[2021,1,1,0,0,0,1,1,4,5,6,53,2020]} +{"unit":"date","value":18630,"calendar":"2021-01-03","parts":[2021,1,3,0,0,0,1,3,6,7,1,53,2020]} +{"unit":"date","value":18631,"calendar":"2021-01-04","parts":[2021,1,4,0,0,0,1,4,0,1,2,1,2021]} +{"unit":"date","value":19782,"calendar":"2024-02-29","parts":[2024,2,29,0,0,0,1,60,3,4,5,9,2024]} +{"unit":"date","value":20086,"calendar":"2024-12-29","parts":[2024,12,29,0,0,0,4,364,6,7,1,52,2024]} +{"unit":"date","value":20087,"calendar":"2024-12-30","parts":[2024,12,30,0,0,0,4,365,0,1,2,1,2025]} +{"unit":"date","value":20089,"calendar":"2025-01-01","parts":[2025,1,1,0,0,0,1,1,2,3,4,1,2025]} +{"unit":"ms","value":-9223372036854775808,"millis":-9223372036854775808,"nanos":0,"calendar":"+1705471-09-26T16:47:04.192","parts":[1705471,9,26,16,47,4,3,269,1,2,3,39,1705471]} +{"unit":"ms","value":9223372036854775807,"millis":9223372036854775807,"nanos":0,"calendar":"-1701532-04-06T07:12:55.807","parts":[-1701532,4,6,7,12,55,2,97,4,5,6,14,-1701532]} +{"unit":"ms","value":-1,"millis":-1,"nanos":0,"calendar":"1969-12-31T23:59:59.999","parts":[1969,12,31,23,59,59,4,365,2,3,4,1,1970]} +{"unit":"ms","value":0,"millis":0,"nanos":0,"calendar":"1970-01-01T00:00","parts":[1970,1,1,0,0,0,1,1,3,4,5,1,1970]} +{"unit":"ms","value":1,"millis":1,"nanos":0,"calendar":"1970-01-01T00:00:00.001","parts":[1970,1,1,0,0,0,1,1,3,4,5,1,1970]} +{"unit":"ms","value":-1001,"millis":-1001,"nanos":0,"calendar":"1969-12-31T23:59:58.999","parts":[1969,12,31,23,59,58,4,365,2,3,4,1,1970]} +{"unit":"ms","value":-1000,"millis":-1000,"nanos":0,"calendar":"1969-12-31T23:59:59","parts":[1969,12,31,23,59,59,4,365,2,3,4,1,1970]} +{"unit":"ms","value":-999,"millis":-999,"nanos":0,"calendar":"1969-12-31T23:59:59.001","parts":[1969,12,31,23,59,59,4,365,2,3,4,1,1970]} +{"unit":"ms","value":999,"millis":999,"nanos":0,"calendar":"1970-01-01T00:00:00.999","parts":[1970,1,1,0,0,0,1,1,3,4,5,1,1970]} +{"unit":"ms","value":1000,"millis":1000,"nanos":0,"calendar":"1970-01-01T00:00:01","parts":[1970,1,1,0,0,1,1,1,3,4,5,1,1970]} +{"unit":"ms","value":1001,"millis":1001,"nanos":0,"calendar":"1970-01-01T00:00:01.001","parts":[1970,1,1,0,0,1,1,1,3,4,5,1,1970]} +{"unit":"ms","value":-86400001,"millis":-86400001,"nanos":0,"calendar":"1969-12-30T23:59:59.999","parts":[1969,12,30,23,59,59,4,364,1,2,3,1,1970]} +{"unit":"ms","value":-86400000,"millis":-86400000,"nanos":0,"calendar":"1969-12-31T00:00","parts":[1969,12,31,0,0,0,4,365,2,3,4,1,1970]} +{"unit":"ms","value":-86399999,"millis":-86399999,"nanos":0,"calendar":"1969-12-31T00:00:00.001","parts":[1969,12,31,0,0,0,4,365,2,3,4,1,1970]} +{"unit":"ms","value":86399999,"millis":86399999,"nanos":0,"calendar":"1970-01-01T23:59:59.999","parts":[1970,1,1,23,59,59,1,1,3,4,5,1,1970]} +{"unit":"ms","value":86400000,"millis":86400000,"nanos":0,"calendar":"1970-01-02T00:00","parts":[1970,1,2,0,0,0,1,2,4,5,6,1,1970]} +{"unit":"ms","value":86400001,"millis":86400001,"nanos":0,"calendar":"1970-01-02T00:00:00.001","parts":[1970,1,2,0,0,0,1,2,4,5,6,1,1970]} +{"unit":"ms","value":-185542587187200000,"millis":-185542587187200000,"nanos":0,"calendar":"-5877641-06-23T00:00","parts":[-5877641,6,23,0,0,0,2,174,1,2,3,26,-5877641]} +{"unit":"ms","value":-185542587187200001,"millis":-185542587187200001,"nanos":0,"calendar":"+5881580-07-11T23:59:59.999","parts":[5881580,7,11,23,59,59,3,193,4,5,6,28,5881580]} +{"unit":"ms","value":185542587100800000,"millis":185542587100800000,"nanos":0,"calendar":"+5881580-07-11T00:00","parts":[5881580,7,11,0,0,0,3,193,4,5,6,28,5881580]} +{"unit":"ms","value":185542587187199999,"millis":185542587187199999,"nanos":0,"calendar":"+5881580-07-11T23:59:59.999","parts":[5881580,7,11,23,59,59,3,193,4,5,6,28,5881580]} +{"unit":"ms","value":185542587187200000,"millis":185542587187200000,"nanos":0,"calendar":"-5877641-06-23T00:00","parts":[-5877641,6,23,0,0,0,2,174,1,2,3,26,-5877641]} +{"unit":"ms","value":371085174374400000,"millis":371085174374400000,"nanos":0,"calendar":"1970-01-01T00:00","parts":[1970,1,1,0,0,0,1,1,3,4,5,1,1970]} +{"unit":"ms","value":-371085174374400000,"millis":-371085174374400000,"nanos":0,"calendar":"1970-01-01T00:00","parts":[1970,1,1,0,0,0,1,1,3,4,5,1,1970]} +{"unit":"us","value":-9223372036854775808,"millis":-9223372036854776,"nanos":192000,"calendar":"-290308-12-21T19:59:05.224192","parts":[-290308,12,21,19,59,5,4,356,6,7,1,51,-290308]} +{"unit":"us","value":9223372036854775807,"millis":9223372036854775,"nanos":807000,"calendar":"+294247-01-10T04:00:54.775807","parts":[294247,1,10,4,0,54,1,10,6,7,1,1,294247]} +{"unit":"us","value":-1,"millis":-1,"nanos":999000,"calendar":"1969-12-31T23:59:59.999999","parts":[1969,12,31,23,59,59,4,365,2,3,4,1,1970]} +{"unit":"us","value":0,"millis":0,"nanos":0,"calendar":"1970-01-01T00:00","parts":[1970,1,1,0,0,0,1,1,3,4,5,1,1970]} +{"unit":"us","value":1,"millis":0,"nanos":1000,"calendar":"1970-01-01T00:00:00.000001","parts":[1970,1,1,0,0,0,1,1,3,4,5,1,1970]} +{"unit":"us","value":-1000001,"millis":-1001,"nanos":999000,"calendar":"1969-12-31T23:59:58.999999","parts":[1969,12,31,23,59,58,4,365,2,3,4,1,1970]} +{"unit":"us","value":-1000000,"millis":-1000,"nanos":0,"calendar":"1969-12-31T23:59:59","parts":[1969,12,31,23,59,59,4,365,2,3,4,1,1970]} +{"unit":"us","value":-999999,"millis":-1000,"nanos":1000,"calendar":"1969-12-31T23:59:59.000001","parts":[1969,12,31,23,59,59,4,365,2,3,4,1,1970]} +{"unit":"us","value":999999,"millis":999,"nanos":999000,"calendar":"1970-01-01T00:00:00.999999","parts":[1970,1,1,0,0,0,1,1,3,4,5,1,1970]} +{"unit":"us","value":1000000,"millis":1000,"nanos":0,"calendar":"1970-01-01T00:00:01","parts":[1970,1,1,0,0,1,1,1,3,4,5,1,1970]} +{"unit":"us","value":1000001,"millis":1000,"nanos":1000,"calendar":"1970-01-01T00:00:01.000001","parts":[1970,1,1,0,0,1,1,1,3,4,5,1,1970]} +{"unit":"us","value":-86400000001,"millis":-86400001,"nanos":999000,"calendar":"1969-12-30T23:59:59.999999","parts":[1969,12,30,23,59,59,4,364,1,2,3,1,1970]} +{"unit":"us","value":-86400000000,"millis":-86400000,"nanos":0,"calendar":"1969-12-31T00:00","parts":[1969,12,31,0,0,0,4,365,2,3,4,1,1970]} +{"unit":"us","value":-86399999999,"millis":-86400000,"nanos":1000,"calendar":"1969-12-31T00:00:00.000001","parts":[1969,12,31,0,0,0,4,365,2,3,4,1,1970]} +{"unit":"us","value":86399999999,"millis":86399999,"nanos":999000,"calendar":"1970-01-01T23:59:59.999999","parts":[1970,1,1,23,59,59,1,1,3,4,5,1,1970]} +{"unit":"us","value":86400000000,"millis":86400000,"nanos":0,"calendar":"1970-01-02T00:00","parts":[1970,1,2,0,0,0,1,2,4,5,6,1,1970]} +{"unit":"us","value":86400000001,"millis":86400000,"nanos":1000,"calendar":"1970-01-02T00:00:00.000001","parts":[1970,1,2,0,0,0,1,2,4,5,6,1,1970]} +{"unit":"ns","value":-9223372036854775808,"millis":-9223372036855,"nanos":224192,"calendar":"1677-09-21T00:12:43.145224192","parts":[1677,9,21,0,12,43,3,264,1,2,3,38,1677]} +{"unit":"ns","value":9223372036854775807,"millis":9223372036854,"nanos":775807,"calendar":"2262-04-11T23:47:16.854775807","parts":[2262,4,11,23,47,16,2,101,4,5,6,15,2262]} +{"unit":"ns","value":-1,"millis":-1,"nanos":999999,"calendar":"1969-12-31T23:59:59.999999999","parts":[1969,12,31,23,59,59,4,365,2,3,4,1,1970]} +{"unit":"ns","value":0,"millis":0,"nanos":0,"calendar":"1970-01-01T00:00","parts":[1970,1,1,0,0,0,1,1,3,4,5,1,1970]} +{"unit":"ns","value":1,"millis":0,"nanos":1,"calendar":"1970-01-01T00:00:00.000000001","parts":[1970,1,1,0,0,0,1,1,3,4,5,1,1970]} +{"unit":"ns","value":-1000000001,"millis":-1001,"nanos":999999,"calendar":"1969-12-31T23:59:58.999999999","parts":[1969,12,31,23,59,58,4,365,2,3,4,1,1970]} +{"unit":"ns","value":-1000000000,"millis":-1000,"nanos":0,"calendar":"1969-12-31T23:59:59","parts":[1969,12,31,23,59,59,4,365,2,3,4,1,1970]} +{"unit":"ns","value":-999999999,"millis":-1000,"nanos":1,"calendar":"1969-12-31T23:59:59.000000001","parts":[1969,12,31,23,59,59,4,365,2,3,4,1,1970]} +{"unit":"ns","value":999999999,"millis":999,"nanos":999999,"calendar":"1970-01-01T00:00:00.999999999","parts":[1970,1,1,0,0,0,1,1,3,4,5,1,1970]} +{"unit":"ns","value":1000000000,"millis":1000,"nanos":0,"calendar":"1970-01-01T00:00:01","parts":[1970,1,1,0,0,1,1,1,3,4,5,1,1970]} +{"unit":"ns","value":1000000001,"millis":1000,"nanos":1,"calendar":"1970-01-01T00:00:01.000000001","parts":[1970,1,1,0,0,1,1,1,3,4,5,1,1970]} +{"unit":"ns","value":-86400000000001,"millis":-86400001,"nanos":999999,"calendar":"1969-12-30T23:59:59.999999999","parts":[1969,12,30,23,59,59,4,364,1,2,3,1,1970]} +{"unit":"ns","value":-86400000000000,"millis":-86400000,"nanos":0,"calendar":"1969-12-31T00:00","parts":[1969,12,31,0,0,0,4,365,2,3,4,1,1970]} +{"unit":"ns","value":-86399999999999,"millis":-86400000,"nanos":1,"calendar":"1969-12-31T00:00:00.000000001","parts":[1969,12,31,0,0,0,4,365,2,3,4,1,1970]} +{"unit":"ns","value":86399999999999,"millis":86399999,"nanos":999999,"calendar":"1970-01-01T23:59:59.999999999","parts":[1970,1,1,23,59,59,1,1,3,4,5,1,1970]} +{"unit":"ns","value":86400000000000,"millis":86400000,"nanos":0,"calendar":"1970-01-02T00:00","parts":[1970,1,2,0,0,0,1,2,4,5,6,1,1970]} +{"unit":"ns","value":86400000000001,"millis":86400000,"nanos":1,"calendar":"1970-01-02T00:00:00.000000001","parts":[1970,1,2,0,0,0,1,2,4,5,6,1,1970]} +{"unit":"s","value":-9223372036854775,"millis":-9223372036854775000,"nanos":0,"calendar":"+1705471-09-26T16:47:05","parts":[1705471,9,26,16,47,5,3,269,1,2,3,39,1705471]} +{"unit":"s","value":9223372036854775,"millis":9223372036854775000,"nanos":0,"calendar":"-1701532-04-06T07:12:55","parts":[-1701532,4,6,7,12,55,2,97,4,5,6,14,-1701532]} +{"unit":"s","value":-86401,"millis":-86401000,"nanos":0,"calendar":"1969-12-30T23:59:59","parts":[1969,12,30,23,59,59,4,364,1,2,3,1,1970]} +{"unit":"s","value":-86400,"millis":-86400000,"nanos":0,"calendar":"1969-12-31T00:00","parts":[1969,12,31,0,0,0,4,365,2,3,4,1,1970]} +{"unit":"s","value":-86399,"millis":-86399000,"nanos":0,"calendar":"1969-12-31T00:00:01","parts":[1969,12,31,0,0,1,4,365,2,3,4,1,1970]} +{"unit":"s","value":-1,"millis":-1000,"nanos":0,"calendar":"1969-12-31T23:59:59","parts":[1969,12,31,23,59,59,4,365,2,3,4,1,1970]} +{"unit":"s","value":0,"millis":0,"nanos":0,"calendar":"1970-01-01T00:00","parts":[1970,1,1,0,0,0,1,1,3,4,5,1,1970]} +{"unit":"s","value":1,"millis":1000,"nanos":0,"calendar":"1970-01-01T00:00:01","parts":[1970,1,1,0,0,1,1,1,3,4,5,1,1970]} +{"unit":"s","value":86399,"millis":86399000,"nanos":0,"calendar":"1970-01-01T23:59:59","parts":[1970,1,1,23,59,59,1,1,3,4,5,1,1970]} +{"unit":"s","value":86400,"millis":86400000,"nanos":0,"calendar":"1970-01-02T00:00","parts":[1970,1,2,0,0,0,1,2,4,5,6,1,1970]} +{"unit":"s","value":86401,"millis":86401000,"nanos":0,"calendar":"1970-01-02T00:00:01","parts":[1970,1,2,0,0,1,1,2,4,5,6,1,1970]} +{"unit":"date","value":-719163,"calendar":"0000-12-31","parts":[0,12,31,0,0,0,4,366,6,7,1,52,0]} +{"unit":"date","value":-719163,"calendar":"0000-12-31","parts":[0,12,31,0,0,0,4,366,6,7,1,52,0]} +{"unit":"date","value":-719162,"calendar":"0001-01-01","parts":[1,1,1,0,0,0,1,1,0,1,2,1,1]} +{"unit":"date","value":-719161,"calendar":"0001-01-02","parts":[1,1,2,0,0,0,1,2,1,2,3,1,1]} +{"unit":"date","value":-719162,"calendar":"0001-01-01","parts":[1,1,1,0,0,0,1,1,0,1,2,1,1]} +{"unit":"date","value":-719161,"calendar":"0001-01-02","parts":[1,1,2,0,0,0,1,2,1,2,3,1,1]} +{"unit":"date","value":2932896,"calendar":"9999-12-31","parts":[9999,12,31,0,0,0,4,365,4,5,6,52,9999]} +{"unit":"date","value":2932896,"calendar":"9999-12-31","parts":[9999,12,31,0,0,0,4,365,4,5,6,52,9999]} +{"unit":"date","value":2932897,"calendar":"+10000-01-01","parts":[10000,1,1,0,0,0,1,1,5,6,7,52,9999]} +{"unit":"date","value":2932898,"calendar":"+10000-01-02","parts":[10000,1,2,0,0,0,1,2,6,7,1,52,9999]} +{"unit":"date","value":2932897,"calendar":"+10000-01-01","parts":[10000,1,1,0,0,0,1,1,5,6,7,52,9999]} +{"unit":"date","value":2932898,"calendar":"+10000-01-02","parts":[10000,1,2,0,0,0,1,2,6,7,1,52,9999]} +{"unit":"s","value":-62135683200,"millis":-62135683200000,"nanos":0,"calendar":"0000-12-31T00:00","parts":[0,12,31,0,0,0,4,366,6,7,1,52,0]} +{"unit":"s","value":-62135596801,"millis":-62135596801000,"nanos":0,"calendar":"0000-12-31T23:59:59","parts":[0,12,31,23,59,59,4,366,6,7,1,52,0]} +{"unit":"s","value":-62135596800,"millis":-62135596800000,"nanos":0,"calendar":"0001-01-01T00:00","parts":[1,1,1,0,0,0,1,1,0,1,2,1,1]} +{"unit":"s","value":-62135596799,"millis":-62135596799000,"nanos":0,"calendar":"0001-01-01T00:00:01","parts":[1,1,1,0,0,1,1,1,0,1,2,1,1]} +{"unit":"s","value":-62135510401,"millis":-62135510401000,"nanos":0,"calendar":"0001-01-01T23:59:59","parts":[1,1,1,23,59,59,1,1,0,1,2,1,1]} +{"unit":"s","value":-62135510400,"millis":-62135510400000,"nanos":0,"calendar":"0001-01-02T00:00","parts":[1,1,2,0,0,0,1,2,1,2,3,1,1]} +{"unit":"s","value":253402214400,"millis":253402214400000,"nanos":0,"calendar":"9999-12-31T00:00","parts":[9999,12,31,0,0,0,4,365,4,5,6,52,9999]} +{"unit":"s","value":253402300799,"millis":253402300799000,"nanos":0,"calendar":"9999-12-31T23:59:59","parts":[9999,12,31,23,59,59,4,365,4,5,6,52,9999]} +{"unit":"s","value":253402300800,"millis":253402300800000,"nanos":0,"calendar":"+10000-01-01T00:00","parts":[10000,1,1,0,0,0,1,1,5,6,7,52,9999]} +{"unit":"s","value":253402300801,"millis":253402300801000,"nanos":0,"calendar":"+10000-01-01T00:00:01","parts":[10000,1,1,0,0,1,1,1,5,6,7,52,9999]} +{"unit":"s","value":253402387199,"millis":253402387199000,"nanos":0,"calendar":"+10000-01-01T23:59:59","parts":[10000,1,1,23,59,59,1,1,5,6,7,52,9999]} +{"unit":"s","value":253402387200,"millis":253402387200000,"nanos":0,"calendar":"+10000-01-02T00:00","parts":[10000,1,2,0,0,0,1,2,6,7,1,52,9999]} +{"unit":"ms","value":-62135683200000,"millis":-62135683200000,"nanos":0,"calendar":"0000-12-31T00:00","parts":[0,12,31,0,0,0,4,366,6,7,1,52,0]} +{"unit":"ms","value":-62135596800001,"millis":-62135596800001,"nanos":0,"calendar":"0000-12-31T23:59:59.999","parts":[0,12,31,23,59,59,4,366,6,7,1,52,0]} +{"unit":"ms","value":-62135596800000,"millis":-62135596800000,"nanos":0,"calendar":"0001-01-01T00:00","parts":[1,1,1,0,0,0,1,1,0,1,2,1,1]} +{"unit":"ms","value":-62135596799999,"millis":-62135596799999,"nanos":0,"calendar":"0001-01-01T00:00:00.001","parts":[1,1,1,0,0,0,1,1,0,1,2,1,1]} +{"unit":"ms","value":-62135510400001,"millis":-62135510400001,"nanos":0,"calendar":"0001-01-01T23:59:59.999","parts":[1,1,1,23,59,59,1,1,0,1,2,1,1]} +{"unit":"ms","value":-62135510400000,"millis":-62135510400000,"nanos":0,"calendar":"0001-01-02T00:00","parts":[1,1,2,0,0,0,1,2,1,2,3,1,1]} +{"unit":"ms","value":253402214400000,"millis":253402214400000,"nanos":0,"calendar":"9999-12-31T00:00","parts":[9999,12,31,0,0,0,4,365,4,5,6,52,9999]} +{"unit":"ms","value":253402300799999,"millis":253402300799999,"nanos":0,"calendar":"9999-12-31T23:59:59.999","parts":[9999,12,31,23,59,59,4,365,4,5,6,52,9999]} +{"unit":"ms","value":253402300800000,"millis":253402300800000,"nanos":0,"calendar":"+10000-01-01T00:00","parts":[10000,1,1,0,0,0,1,1,5,6,7,52,9999]} +{"unit":"ms","value":253402300800001,"millis":253402300800001,"nanos":0,"calendar":"+10000-01-01T00:00:00.001","parts":[10000,1,1,0,0,0,1,1,5,6,7,52,9999]} +{"unit":"ms","value":253402387199999,"millis":253402387199999,"nanos":0,"calendar":"+10000-01-01T23:59:59.999","parts":[10000,1,1,23,59,59,1,1,5,6,7,52,9999]} +{"unit":"ms","value":253402387200000,"millis":253402387200000,"nanos":0,"calendar":"+10000-01-02T00:00","parts":[10000,1,2,0,0,0,1,2,6,7,1,52,9999]} +{"unit":"us","value":-62135683200000000,"millis":-62135683200000,"nanos":0,"calendar":"0000-12-31T00:00","parts":[0,12,31,0,0,0,4,366,6,7,1,52,0]} +{"unit":"us","value":-62135596800000001,"millis":-62135596800001,"nanos":999000,"calendar":"0000-12-31T23:59:59.999999","parts":[0,12,31,23,59,59,4,366,6,7,1,52,0]} +{"unit":"us","value":-62135596800000000,"millis":-62135596800000,"nanos":0,"calendar":"0001-01-01T00:00","parts":[1,1,1,0,0,0,1,1,0,1,2,1,1]} +{"unit":"us","value":-62135596799999999,"millis":-62135596800000,"nanos":1000,"calendar":"0001-01-01T00:00:00.000001","parts":[1,1,1,0,0,0,1,1,0,1,2,1,1]} +{"unit":"us","value":-62135510400000001,"millis":-62135510400001,"nanos":999000,"calendar":"0001-01-01T23:59:59.999999","parts":[1,1,1,23,59,59,1,1,0,1,2,1,1]} +{"unit":"us","value":-62135510400000000,"millis":-62135510400000,"nanos":0,"calendar":"0001-01-02T00:00","parts":[1,1,2,0,0,0,1,2,1,2,3,1,1]} +{"unit":"us","value":253402214400000000,"millis":253402214400000,"nanos":0,"calendar":"9999-12-31T00:00","parts":[9999,12,31,0,0,0,4,365,4,5,6,52,9999]} +{"unit":"us","value":253402300799999999,"millis":253402300799999,"nanos":999000,"calendar":"9999-12-31T23:59:59.999999","parts":[9999,12,31,23,59,59,4,365,4,5,6,52,9999]} +{"unit":"us","value":253402300800000000,"millis":253402300800000,"nanos":0,"calendar":"+10000-01-01T00:00","parts":[10000,1,1,0,0,0,1,1,5,6,7,52,9999]} +{"unit":"us","value":253402300800000001,"millis":253402300800000,"nanos":1000,"calendar":"+10000-01-01T00:00:00.000001","parts":[10000,1,1,0,0,0,1,1,5,6,7,52,9999]} +{"unit":"us","value":253402387199999999,"millis":253402387199999,"nanos":999000,"calendar":"+10000-01-01T23:59:59.999999","parts":[10000,1,1,23,59,59,1,1,5,6,7,52,9999]} +{"unit":"us","value":253402387200000000,"millis":253402387200000,"nanos":0,"calendar":"+10000-01-02T00:00","parts":[10000,1,2,0,0,0,1,2,6,7,1,52,9999]}