Skip to content

Commit e81837d

Browse files
committed
Allow DateTime format specified in CLICS/ISO_8601
This fixes the following situation: - Upload a valid contest either via UI or API. - Shadow the contest and receive a valid CLICS start_time update - In the UI we would validate the contest and consider the contest invalid. In the contest validation we only considered the DateTime format with timezones and not offsets - The UI for rejudgings is now also locked as we validate the contest on multiple pages. To make this easier to read the regex is now also extracted to separate variables. That way it was easier to verify that our relative times are escaped correctly (+ is special for regex). Also added some smaller parts: - The `+` seems optional for relative times See the CLICS spec: https://github.com/icpc/ccs-specs/blame/master/Contest_API.md#L235. - Allow also only 1 subsecond for absolute positive URLs - We now also allow: yyyy-mm-ddThh:mm:ss(.uuu)?[+-]zz(:mm)? besides (T instead of space between date & time) yyyy-mm-dd hh:mm:ss(.uuu)?[+-]zz(:mm)? See: https://github.com/icpc/ccs-specs/blame/master/Contest_API.md#L233 - Allow for the Z shorthand for UTC - Disallow different offsets in the contest times. CLICS allows for this but we can fix this if there is ever a real usecase for it. We already did this for timezones.
1 parent 65af5fe commit e81837d

File tree

4 files changed

+107
-8
lines changed

4 files changed

+107
-8
lines changed

webapp/src/Controller/Jury/ContestController.php

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -956,9 +956,24 @@ private function checkTimezones(FormInterface $form): ?Response
956956
foreach (['Activate', 'Deactivate', 'Start', 'End', 'Freeze', 'Unfreeze'] as $timeString) {
957957
$tmpValue = $formData->{'get' . $timeString . 'timeString'}();
958958
if ($tmpValue !== '' && !is_null($tmpValue)) {
959-
$fields = explode(' ', $tmpValue);
960-
if (count($fields) > 1) {
961-
$timeZones[] = $fields[2];
959+
if (preg_match("/\d{2}-\d{2}-\d{2}.*/", $tmpValue) === 1) {
960+
$chr = $tmpValue[10]; // The separator between date & time
961+
$fields = explode($chr, $tmpValue);
962+
// First field is the time, 2nd/3th might be timezone or offset
963+
$tmpValue = substr(str_replace($fields[0], '', $tmpValue), 1); // Also remove the separator
964+
if (str_contains($tmpValue, ' ')) {
965+
$fields = explode(' ', $tmpValue);
966+
} elseif (str_contains($tmpValue, '+')) {
967+
$fields = explode('+', $tmpValue);
968+
} elseif (str_contains($tmpValue, '-')) {
969+
$fields = explode('-', $tmpValue);
970+
} elseif (substr($tmpValue, -1) === 'Z') {
971+
$timeZones[] = 'UTC';
972+
continue;
973+
}
974+
if (count($fields) > 1) {
975+
$timeZones[] = $fields[1];
976+
}
962977
}
963978
}
964979
}

webapp/src/Entity/Contest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1293,7 +1293,7 @@ public function validate(ExecutionContextInterface $context): void
12931293
$this->updateTimes();
12941294
if (Utils::difftime((float)$this->getEndtime(), (float)$this->getStarttime(true)) <= 0) {
12951295
$context
1296-
->buildViolation('Contest ends before it even starts')
1296+
->buildViolation('Contest ends before it even starts.')
12971297
->atPath('endtimeString')
12981298
->addViolation();
12991299
}

webapp/src/Validator/Constraints/TimeStringValidator.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ class TimeStringValidator extends ConstraintValidator
1010
{
1111
public function validate(mixed $value, Constraint $constraint): void
1212
{
13+
$timezoneRegex = "[A-Za-z][A-Za-z0-9_\/+-]{1,35}"; # See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
14+
$offsetRegex = "[+-]\d{1,2}(:\d\d)?";
15+
$absoluteRegex = "\d\d\d\d-\d\d-\d\d( |T)\d\d:\d\d:\d\d(\.\d{1,6})?( " . $timezoneRegex . "|" . $offsetRegex . "|Z)";
16+
$relativeRegex = "\d+:\d\d(:\d\d(\.\d{1,6})?)?";
1317
if (!$constraint instanceof TimeString) {
1418
throw new UnexpectedTypeException($constraint, TimeString::class);
1519
}
@@ -24,11 +28,11 @@ public function validate(mixed $value, Constraint $constraint): void
2428

2529
if ($constraint->allowRelative) {
2630
$regex = $constraint->relativeIsPositive ?
27-
"/^(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d(\.\d{1,6})? [A-Za-z][A-Za-z0-9_\/+-]{1,35}|\+\d+:\d\d(:\d\d(\.\d{1,6})?)?)$/" :
28-
"/^(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d(\.\d{1,6})? [A-Za-z][A-Za-z0-9_\/+-]{1,35}|-\d+:\d\d(:\d\d(\.\d{1,6})?)?)$/";
31+
"/^(" . $absoluteRegex . "|\+?" . $relativeRegex . ")$/" :
32+
"/^(" . $absoluteRegex . "|-" . $relativeRegex . ")$/";
2933
$message = $constraint->absoluteRelativeMessage;
3034
} else {
31-
$regex = "/^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d(\.\d{1,6})? [A-Za-z][A-Za-z0-9_\/+-]{1,35}$/";
35+
$regex = "/^" . $absoluteRegex . "$/";
3236
$message = $constraint->absoluteMessage;
3337
}
3438
if (preg_match($regex, $value) !== 1) {

webapp/tests/Unit/Controller/Jury/ContestControllerTest.php

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,54 @@ class ContestControllerTest extends JuryControllerTestCase
4949
'silverMedals' => '1',
5050
'bronzeMedals' => '1',
5151
'medalCategories' => ['0' => '2']],
52+
['shortname' => 'CLICS_offset_HMM',
53+
'name' => 'No Timezone but only offset',
54+
'activatetimeString' => '2021-07-17 16:08:00+1:11',
55+
'starttimeString' => '2021-07-17 16:09:00+1:11',
56+
'freezetimeString' => '2021-07-17 16:10:00+1:11',
57+
'endtimeString' => '2021-07-17 16:11:00+1:11',
58+
'unfreezetimeString' => '2021-07-17T16:12:00+1:11',
59+
'deactivatetimeString' => '2021-07-17T16:13:00+1:11'],
60+
['shortname' => 'CLICS_offset_HHMM',
61+
'name' => 'No Timezone but only offset',
62+
'activatetimeString' => '2021-07-17 16:08:00+11:11',
63+
'starttimeString' => '2021-07-17 16:09:00+11:11',
64+
'freezetimeString' => '2021-07-17 16:10:00+11:11',
65+
'endtimeString' => '2021-07-17 16:11:00+11:11',
66+
'unfreezetimeString' => '2021-07-17T16:12:00+11:11',
67+
'deactivatetimeString' => '2021-07-17T16:13:00+11:11'],
68+
['shortname' => 'CLICS_offset_0H00',
69+
'name' => 'No Timezone but only offset',
70+
'activatetimeString' => '2021-07-17 16:08:00+01:00',
71+
'starttimeString' => '2021-07-17 16:09:00+01:00',
72+
'freezetimeString' => '2021-07-17 16:10:00+01:00',
73+
'endtimeString' => '2021-07-17 16:11:00+01:00',
74+
'unfreezetimeString' => '2021-07-17T16:12:00+01:00',
75+
'deactivatetimeString' => '2021-07-17T16:13:00+01:00'],
76+
['shortname' => 'CLICS_offset_H',
77+
'name' => 'No Timezone but only offset',
78+
'activatetimeString' => '2021-07-17 16:08:00+1',
79+
'starttimeString' => '2021-07-17 16:09:00+1',
80+
'freezetimeString' => '2021-07-17 16:10:00+1',
81+
'endtimeString' => '2021-07-17 16:11:00+1',
82+
'unfreezetimeString' => '2021-07-17T16:12:00+1',
83+
'deactivatetimeString' => '2021-07-17T16:13:00+1'],
84+
['shortname' => 'CLICS_offset_-HHH',
85+
'name' => 'No Timezone but only offset',
86+
'activatetimeString' => '2021-07-17 16:08:00-01',
87+
'starttimeString' => '2021-07-17 16:09:00-01',
88+
'freezetimeString' => '2021-07-17 16:10:00-01',
89+
'endtimeString' => '2021-07-17 16:11:00-01',
90+
'unfreezetimeString' => '2021-07-17T16:12:00-01',
91+
'deactivatetimeString' => '2021-07-17T16:13:00-01'],
92+
['shortname' => 'utc_Z',
93+
'name' => 'UTC (Z)',
94+
'activatetimeString' => '2021-07-17 16:08:00Z',
95+
'starttimeString' => '2021-07-17 16:09:00.0Z',
96+
'freezetimeString' => '2021-07-17 16:10:00.00Z',
97+
'endtimeString' => '2021-07-17 16:11:00.000Z',
98+
'unfreezetimeString' => '2021-07-17T16:12:00.1Z',
99+
'deactivatetimeString' => '2021-07-17T16:13:00.2Z'],
52100
['shortname' => 'otzcet',
53101
'name' => 'Other timezone (CET)',
54102
'activatetimeString' => '2021-07-17 16:08:00 CET',
@@ -57,6 +105,14 @@ class ContestControllerTest extends JuryControllerTestCase
57105
'endtimeString' => '2021-07-17 16:11:00 CET',
58106
'unfreezetimeString' => '2021-07-17 16:12:00 CET',
59107
'deactivatetimeString' => '2021-07-17 16:13:00 CET'],
108+
['shortname' => 'otzAfricaPorto-Novo',
109+
'name' => 'Other timezone (Africa/Porto-Novo)',
110+
'activatetimeString' => '2021-07-17 16:08:00 Africa/Porto-Novo',
111+
'starttimeString' => '2021-07-17 16:09:00 Africa/Porto-Novo',
112+
'freezetimeString' => '2021-07-17 16:10:00 Africa/Porto-Novo',
113+
'endtimeString' => '2021-07-17 16:11:00 Africa/Porto-Novo',
114+
'unfreezetimeString' => '2021-07-17 16:12:00 Africa/Porto-Novo',
115+
'deactivatetimeString' => '2021-07-17 16:13:00 Africa/Porto-Novo'],
60116
['shortname' => 'otzunder',
61117
'name' => 'Other timezone (Underscore)',
62118
'activatetimeString' => '2021-07-17 16:08:00 America/Porto_Velho',
@@ -73,6 +129,14 @@ class ContestControllerTest extends JuryControllerTestCase
73129
'endtimeString' => '2021-07-17 16:11:00 Etc/GMT-3',
74130
'unfreezetimeString' => '',
75131
'deactivatetimeString' => ''],
132+
['shortname' => 'otzGMT2',
133+
'name' => 'Other timezone (GMT)',
134+
'activatetimeString' => '2021-07-17 16:08:00 Etc/GMT+3',
135+
'starttimeString' => '2021-07-17 16:09:00 Etc/GMT+3',
136+
'freezetimeString' => '2021-07-17 16:10:00 Etc/GMT+3',
137+
'endtimeString' => '2021-07-17 16:11:00 Etc/GMT+3',
138+
'unfreezetimeString' => '',
139+
'deactivatetimeString' => ''],
76140
['shortname' => 'otzrel',
77141
'name' => 'Other timezone (Relative)',
78142
'activatetimeString' => '-10:00',
@@ -81,6 +145,14 @@ class ContestControllerTest extends JuryControllerTestCase
81145
'endtimeString' => '+1111:11',
82146
'unfreezetimeString' => '',
83147
'deactivatetimeString' => ''],
148+
['shortname' => 'other_split_char',
149+
'name' => 'Other CLICS splitchar',
150+
'activatetimeString' => '-10:00',
151+
'starttimeString' => '2021-07-17T16:09:00 Atlantic/Reykjavik',
152+
'freezetimeString' => '+0:01',
153+
'endtimeString' => '+1111:11',
154+
'unfreezetimeString' => '',
155+
'deactivatetimeString' => ''],
84156
['shortname' => 'nofr',
85157
'name' => 'No Freeze',
86158
'freezetimeString' => '',
@@ -93,7 +165,7 @@ class ContestControllerTest extends JuryControllerTestCase
93165
'endtimeString' => '2021-07-17 16:11:00 Europe/Amsterdam',
94166
'unfreezetimeString' => '',
95167
'deactivatetimeString' => ''],
96-
['shortname' => 'dirfreeze',
168+
['shortname' => 'dirfreeze',
97169
'name' => 'Direct freeze minimal',
98170
'activatetimeString' => '2021-07-17 16:07:59 Europe/Amsterdam',
99171
'starttimeString' => '2021-07-17 16:08:00 Europe/Amsterdam',
@@ -109,6 +181,14 @@ class ContestControllerTest extends JuryControllerTestCase
109181
'endtimeString' => '+10:00',
110182
'unfreezetimeString' => '+25:00',
111183
'deactivatetimeString' => ''],
184+
['shortname' => 'dirfreezerelnoplus',
185+
'name' => 'Direct freeze minimal relative',
186+
'activatetimeString' => '-0:00',
187+
'starttimeString' => '2021-07-17 16:08:00 Europe/Amsterdam',
188+
'freezetimeString' => '0:00',
189+
'endtimeString' => '+10:00',
190+
'unfreezetimeString' => '25:00',
191+
'deactivatetimeString' => ''],
112192
['shortname' => 'rel',
113193
'name' => 'Relative contest',
114194
'activatetimeString' => '-1:00',

0 commit comments

Comments
 (0)