Skip to content

Commit 371ad29

Browse files
authored
feat: adds time_format and timestamp_format and associated tests (#2238)
1 parent 7d31828 commit 371ad29

File tree

5 files changed

+133
-0
lines changed

5 files changed

+133
-0
lines changed

google/cloud/bigquery/external_config.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,34 @@ def time_zone(self) -> Optional[str]:
879879
def time_zone(self, value: Optional[str]):
880880
self._properties["timeZone"] = value
881881

882+
@property
883+
def time_format(self) -> Optional[str]:
884+
"""Optional[str]: Format used to parse TIME values. Supports C-style and SQL-style values.
885+
886+
See:
887+
https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#ExternalDataConfiguration.FIELDS.time_format
888+
"""
889+
result = self._properties.get("timeFormat")
890+
return typing.cast(str, result)
891+
892+
@time_format.setter
893+
def time_format(self, value: Optional[str]):
894+
self._properties["timeFormat"] = value
895+
896+
@property
897+
def timestamp_format(self) -> Optional[str]:
898+
"""Optional[str]: Format used to parse TIMESTAMP values. Supports C-style and SQL-style values.
899+
900+
See:
901+
https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#ExternalDataConfiguration.FIELDS.timestamp_format
902+
"""
903+
result = self._properties.get("timestampFormat")
904+
return typing.cast(str, result)
905+
906+
@timestamp_format.setter
907+
def timestamp_format(self, value: Optional[str]):
908+
self._properties["timestampFormat"] = value
909+
882910
@property
883911
def connection_id(self):
884912
"""Optional[str]: [Experimental] ID of a BigQuery Connection API

google/cloud/bigquery/job/load.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,32 @@ def time_zone(self) -> Optional[str]:
575575
def time_zone(self, value: Optional[str]):
576576
self._set_sub_prop("timeZone", value)
577577

578+
@property
579+
def time_format(self) -> Optional[str]:
580+
"""Optional[str]: Date format used for parsing TIME values.
581+
582+
See:
583+
https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.time_format
584+
"""
585+
return self._get_sub_prop("timeFormat")
586+
587+
@time_format.setter
588+
def time_format(self, value: Optional[str]):
589+
self._set_sub_prop("timeFormat", value)
590+
591+
@property
592+
def timestamp_format(self) -> Optional[str]:
593+
"""Optional[str]: Date format used for parsing TIMESTAMP values.
594+
595+
See:
596+
https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobConfigurationLoad.FIELDS.timestamp_format
597+
"""
598+
return self._get_sub_prop("timestampFormat")
599+
600+
@timestamp_format.setter
601+
def timestamp_format(self, value: Optional[str]):
602+
self._set_sub_prop("timestampFormat", value)
603+
578604
@property
579605
def time_partitioning(self):
580606
"""Optional[google.cloud.bigquery.table.TimePartitioning]: Specifies time-based
@@ -930,6 +956,20 @@ def time_zone(self):
930956
"""
931957
return self.configuration.time_zone
932958

959+
@property
960+
def time_format(self):
961+
"""See
962+
:attr:`google.cloud.bigquery.job.LoadJobConfig.time_format`.
963+
"""
964+
return self.configuration.time_format
965+
966+
@property
967+
def timestamp_format(self):
968+
"""See
969+
:attr:`google.cloud.bigquery.job.LoadJobConfig.timestamp_format`.
970+
"""
971+
return self.configuration.timestamp_format
972+
933973
@property
934974
def schema_update_options(self):
935975
"""See

tests/unit/job/test_load.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,18 @@ def _setUpConstants(self):
3939
self.REFERENCE_FILE_SCHEMA_URI = "gs://path/to/reference"
4040
self.DATE_FORMAT = "%Y-%m-%d"
4141
self.TIME_ZONE = "UTC"
42+
self.TIME_FORMAT = "%H:%M:%S"
43+
self.TIMESTAMP_FORMAT = "YYYY-MM-DD HH:MM:SS.SSSSSSZ"
4244

4345
def _make_resource(self, started=False, ended=False):
4446
resource = super(TestLoadJob, self)._make_resource(started, ended)
4547
config = resource["configuration"]["load"]
4648
config["sourceUris"] = [self.SOURCE1]
4749
config["dateFormat"] = self.DATE_FORMAT
4850
config["timeZone"] = self.TIME_ZONE
51+
config["timeFormat"] = self.TIME_FORMAT
52+
config["timestampFormat"] = self.TIMESTAMP_FORMAT
53+
4954
config["destinationTable"] = {
5055
"projectId": self.PROJECT,
5156
"datasetId": self.DS_ID,
@@ -163,6 +168,14 @@ def _verifyResourceProperties(self, job, resource):
163168
self.assertEqual(job.time_zone, config["timeZone"])
164169
else:
165170
self.assertIsNone(job.time_zone)
171+
if "timeFormat" in config:
172+
self.assertEqual(job.time_format, config["timeFormat"])
173+
else:
174+
self.assertIsNone(job.time_format)
175+
if "timestampFormat" in config:
176+
self.assertEqual(job.timestamp_format, config["timestampFormat"])
177+
else:
178+
self.assertIsNone(job.timestamp_format)
166179

167180
def test_ctor(self):
168181
client = _make_client(project=self.PROJECT)
@@ -207,6 +220,8 @@ def test_ctor(self):
207220
self.assertIsNone(job.reference_file_schema_uri)
208221
self.assertIsNone(job.date_format)
209222
self.assertIsNone(job.time_zone)
223+
self.assertIsNone(job.time_format)
224+
self.assertIsNone(job.timestamp_format)
210225

211226
def test_ctor_w_config(self):
212227
from google.cloud.bigquery.schema import SchemaField
@@ -604,7 +619,10 @@ def test_begin_w_alternate_client(self):
604619
"schemaUpdateOptions": [SchemaUpdateOption.ALLOW_FIELD_ADDITION],
605620
"dateFormat": self.DATE_FORMAT,
606621
"timeZone": self.TIME_ZONE,
622+
"timeFormat": self.TIME_FORMAT,
623+
"timestampFormat": self.TIMESTAMP_FORMAT,
607624
}
625+
608626
RESOURCE["configuration"]["load"] = LOAD_CONFIGURATION
609627
conn1 = make_connection()
610628
client1 = _make_client(project=self.PROJECT, connection=conn1)
@@ -634,6 +652,8 @@ def test_begin_w_alternate_client(self):
634652
config.reference_file_schema_uri = "gs://path/to/reference"
635653
config.date_format = self.DATE_FORMAT
636654
config.time_zone = self.TIME_ZONE
655+
config.time_format = self.TIME_FORMAT
656+
config.timestamp_format = self.TIMESTAMP_FORMAT
637657

638658
with mock.patch(
639659
"google.cloud.bigquery.opentelemetry_tracing._get_final_span_attributes"

tests/unit/job/test_load_config.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,40 @@ def test_time_zone_setter(self):
860860
config.time_zone = time_zone
861861
self.assertEqual(config._properties["load"]["timeZone"], time_zone)
862862

863+
def test_time_format_missing(self):
864+
config = self._get_target_class()()
865+
self.assertIsNone(config.time_format)
866+
867+
def test_time_format_hit(self):
868+
time_format = "%H:%M:%S"
869+
config = self._get_target_class()()
870+
config._properties["load"]["timeFormat"] = time_format
871+
self.assertEqual(config.time_format, time_format)
872+
873+
def test_time_format_setter(self):
874+
time_format = "HH24:MI:SS"
875+
config = self._get_target_class()()
876+
config.time_format = time_format
877+
self.assertEqual(config._properties["load"]["timeFormat"], time_format)
878+
879+
def test_timestamp_format_missing(self):
880+
config = self._get_target_class()()
881+
self.assertIsNone(config.timestamp_format)
882+
883+
def test_timestamp_format_hit(self):
884+
timestamp_format = "%Y-%m-%dT%H:%M:%S.%fZ"
885+
config = self._get_target_class()()
886+
config._properties["load"]["timestampFormat"] = timestamp_format
887+
self.assertEqual(config.timestamp_format, timestamp_format)
888+
889+
def test_timestamp_format_setter(self):
890+
timestamp_format = "YYYY/MM/DD HH24:MI:SS.FF6 TZR"
891+
config = self._get_target_class()()
892+
config.timestamp_format = timestamp_format
893+
self.assertEqual(
894+
config._properties["load"]["timestampFormat"], timestamp_format
895+
)
896+
863897
def test_parquet_options_missing(self):
864898
config = self._get_target_class()()
865899
self.assertIsNone(config.parquet_options)

tests/unit/test_external_config.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ class TestExternalConfig(unittest.TestCase):
2727
SOURCE_URIS = ["gs://foo", "gs://bar"]
2828
DATE_FORMAT = "MM/DD/YYYY"
2929
TIME_ZONE = "America/Los_Angeles"
30+
TIME_FORMAT = "HH24:MI:SS"
31+
TIMESTAMP_FORMAT = "MM/DD/YYYY HH24:MI:SS.FF6 TZR"
3032

3133
BASE_RESOURCE = {
3234
"sourceFormat": "",
@@ -37,6 +39,8 @@ class TestExternalConfig(unittest.TestCase):
3739
"compression": "compression",
3840
"dateFormat": DATE_FORMAT,
3941
"timeZone": TIME_ZONE,
42+
"timeFormat": TIME_FORMAT,
43+
"timestampFormat": TIMESTAMP_FORMAT,
4044
}
4145

4246
def test_from_api_repr_base(self):
@@ -85,6 +89,9 @@ def test_to_api_repr_base(self):
8589

8690
ec.date_format = self.DATE_FORMAT
8791
ec.time_zone = self.TIME_ZONE
92+
ec.time_format = self.TIME_FORMAT
93+
ec.timestamp_format = self.TIMESTAMP_FORMAT
94+
8895
exp_schema = {
8996
"fields": [{"name": "full_name", "type": "STRING", "mode": "REQUIRED"}]
9097
}
@@ -100,6 +107,8 @@ def test_to_api_repr_base(self):
100107
"schema": exp_schema,
101108
"dateFormat": self.DATE_FORMAT,
102109
"timeZone": self.TIME_ZONE,
110+
"timeFormat": self.TIME_FORMAT,
111+
"timestampFormat": self.TIMESTAMP_FORMAT,
103112
}
104113
self.assertEqual(got_resource, exp_resource)
105114

@@ -137,6 +146,8 @@ def _verify_base(self, ec):
137146
self.assertEqual(ec.source_uris, self.SOURCE_URIS)
138147
self.assertEqual(ec.date_format, self.DATE_FORMAT)
139148
self.assertEqual(ec.time_zone, self.TIME_ZONE)
149+
self.assertEqual(ec.time_format, self.TIME_FORMAT)
150+
self.assertEqual(ec.timestamp_format, self.TIMESTAMP_FORMAT)
140151

141152
def test_to_api_repr_source_format(self):
142153
ec = external_config.ExternalConfig("CSV")

0 commit comments

Comments
 (0)