1616use App \Utils \Scoreboard \Filter ;
1717use App \Utils \Utils ;
1818use Collator ;
19+ use DateInterval ;
1920use DateTime ;
2021use DateTimeImmutable ;
22+ use DateTimeInterface ;
2123use DateTimeZone ;
2224use Doctrine \ORM \EntityManagerInterface ;
2325use Doctrine \ORM \NonUniqueResultException ;
@@ -83,10 +85,14 @@ public function getContestYamlData(Contest $contest, bool $includeProblems = tru
8385 'formal_name ' => $ contest ->getName (),
8486 'name ' => $ contest ->getShortname (),
8587 'start_time ' => Utils::absTime ($ contest ->getStarttime (), true ),
86- 'end_time ' => Utils::absTime ($ contest ->getEndtime (), true ),
88+ 'end_time ' => Utils::isRelTime ($ contest ->getEndtimeString ())
89+ ? $ contest ->getEndtimeString ()
90+ : Utils::absTime ($ contest ->getEndtime (), true ),
8791 'duration ' => Utils::relTime ($ contest ->getContestTime ((float )$ contest ->getEndtime ())),
8892 'penalty_time ' => $ this ->config ->get ('penalty_time ' ),
89- 'activate_time ' => Utils::absTime ($ contest ->getActivatetime (), true ),
93+ 'activate_time ' => Utils::isRelTime ($ contest ->getActivatetimeString ())
94+ ? $ contest ->getActivatetimeString ()
95+ : Utils::absTime ($ contest ->getActivatetime (), true ),
9096 ];
9197 if ($ warnMsg = $ contest ->getWarningMessage ()) {
9298 $ data ['warning_message ' ] = $ warnMsg ;
@@ -100,21 +106,27 @@ public function getContestYamlData(Contest $contest, bool $includeProblems = tru
100106 }
101107
102108 if ($ contest ->getFreezetime () !== null ) {
103- $ data ['scoreboard_freeze_time ' ] = Utils::absTime ($ contest ->getFreezetime (), true );
109+ $ data ['scoreboard_freeze_time ' ] = Utils::isRelTime ($ contest ->getFreezetimeString ())
110+ ? $ contest ->getFreezetimeString ()
111+ : Utils::absTime ($ contest ->getFreezetime (), true );
104112 $ data ['scoreboard_freeze_duration ' ] = Utils::relTime (
105113 $ contest ->getContestTime ((float )$ contest ->getEndtime ()) - $ contest ->getContestTime ((float )$ contest ->getFreezetime ()),
106114 true ,
107115 );
108116 if ($ contest ->getUnfreezetime () !== null ) {
109- $ data ['scoreboard_thaw_time ' ] = Utils::absTime ($ contest ->getUnfreezetime (), true );
117+ $ data ['scoreboard_thaw_time ' ] = Utils::isRelTime ($ contest ->getUnfreezetimeString ())
118+ ? $ contest ->getUnfreezetimeString ()
119+ : Utils::absTime ($ contest ->getUnfreezetime (), true );
110120 }
111121 }
112122 if ($ contest ->getFinalizetime () !== null ) {
113123 $ data ['finalize_time ' ] = Utils::absTime ($ contest ->getFinalizetime (), true );
114124 }
115125
116126 if ($ contest ->getDeactivatetime () !== null ) {
117- $ data ['deactivate_time ' ] = Utils::absTime ($ contest ->getDeactivatetime (), true );
127+ $ data ['deactivate_time ' ] = Utils::isRelTime ($ contest ->getDeactivatetimeString ())
128+ ? $ contest ->getDeactivatetimeString ()
129+ : Utils::absTime ($ contest ->getDeactivatetime (), true );
118130 }
119131
120132 if ($ includeProblems ) {
@@ -146,9 +158,15 @@ public function getContestYamlData(Contest $contest, bool $includeProblems = tru
146158 *
147159 * @param array<string> $fields
148160 * @param array<string, string|DateTime|DateTimeImmutable> $data
161+ * @return array{time: DateTimeImmutable|null, isRelative: bool|null, relativeTime: string|null}
149162 */
150- protected function convertImportedTime (array $ fields , array $ data , ?string &$ errorMessage = null ): ?DateTimeImmutable
151- {
163+ protected function convertImportedTime (
164+ array $ fields ,
165+ array $ data ,
166+ bool $ allowRelative = true ,
167+ ?DateTimeInterface $ startTime = null ,
168+ ?string &$ errorMessage = null
169+ ): array {
152170 $ timeValue = null ;
153171 $ usedField = null ;
154172 foreach ($ fields as $ field ) {
@@ -160,22 +178,42 @@ protected function convertImportedTime(array $fields, array $data, ?string &$err
160178 }
161179 }
162180
181+ $ isRelative = false ;
182+
163183 if (is_string ($ timeValue )) {
164- $ time = date_create_from_format (DateTime::ISO8601 , $ timeValue ) ?:
165- // Make sure ISO 8601 but with the T replaced with a space also works.
166- date_create_from_format ('Y-m-d H:i:sO ' , $ timeValue );
184+ if ($ allowRelative && ($ isRelative = Utils::isRelTime ($ timeValue )) && $ startTime ) {
185+ $ time = new DateTimeImmutable ($ startTime ->format ('Y-m-d H:i:s ' ), $ startTime ->getTimezone ());
186+ $ seconds = Utils::relTimeToSeconds ($ timeValue );
187+ if ($ seconds < 0 ) {
188+ $ time = $ time ->sub (new DateInterval (sprintf ('PT%sS ' , abs ($ seconds ))));
189+ } else {
190+ $ time = $ time ->add (new DateInterval (sprintf ('PT%sS ' , $ seconds )));
191+ }
192+ } else {
193+ $ time = date_create_from_format (DateTime::ISO8601 , $ timeValue ) ?:
194+ // Make sure ISO 8601 but with the T replaced with a space also works.
195+ date_create_from_format ('Y-m-d H:i:sO ' , $ timeValue );
196+ }
167197 } else {
168198 /** @var DateTime|DateTimeImmutable $time */
169199 $ time = $ timeValue ;
170200 }
171201 // If/When parsing fails we get a false instead of a null
172202 if ($ time === false ) {
173203 $ errorMessage = 'Can not parse ' .$ usedField ;
174- return null ;
204+ return [
205+ 'time ' => null ,
206+ 'isRelative ' => null ,
207+ 'relativeTime ' => null ,
208+ ];
175209 } elseif ($ time ) {
176210 $ time = $ time ->setTimezone (new DateTimeZone (date_default_timezone_get ()));
177211 }
178- return $ time instanceof DateTime ? DateTimeImmutable::createFromMutable ($ time ) : $ time ;
212+ return [
213+ 'time ' => $ time instanceof DateTime ? DateTimeImmutable::createFromMutable ($ time ) : $ time ,
214+ 'isRelative ' => $ isRelative ,
215+ 'relativeTime ' => $ isRelative ? $ timeValue : null ,
216+ ];
179217 }
180218
181219 public function importContestData (mixed $ data , ?string &$ errorMessage = null , ?string &$ cid = null ): bool
@@ -215,13 +253,17 @@ public function importContestData(mixed $data, ?string &$errorMessage = null, ?s
215253
216254 $ invalid_regex = str_replace (['/^[ ' , '+$/ ' ], ['/[^ ' , '/ ' ], DOMJudgeService::EXTERNAL_IDENTIFIER_REGEX );
217255
218- $ startTime = $ this ->convertImportedTime ($ startTimeFields , $ data , $ errorMessage );
256+ [ ' time ' => $ startTime] = $ this ->convertImportedTime ($ startTimeFields , $ data , false , errorMessage: $ errorMessage );
219257 if ($ errorMessage ) {
220258 return false ;
221259 }
222260
223261 // Activate time is special, it can return non empty message for parsing error or null if no field was provided
224- $ activateTime = $ this ->convertImportedTime ($ activateTimeFields , $ data , $ errorMessage );
262+ [
263+ 'time ' => $ activateTime ,
264+ 'isRelative ' => $ activateTimeIsRelative ,
265+ 'relativeTime ' => $ activateRelativeTime ,
266+ ] = $ this ->convertImportedTime ($ activateTimeFields , $ data , startTime: $ startTime , errorMessage: $ errorMessage );
225267 if ($ errorMessage ) {
226268 return false ;
227269 } elseif (!$ activateTime ) {
@@ -231,7 +273,11 @@ public function importContestData(mixed $data, ?string &$errorMessage = null, ?s
231273 }
232274 }
233275
234- $ deactivateTime = $ this ->convertImportedTime ($ deactivateTimeFields , $ data , $ errorMessage );
276+ [
277+ 'time ' => $ deactivateTime ,
278+ 'isRelative ' => $ deactivateTimeIsRelative ,
279+ 'relativeTime ' => $ deactivateRelativeTime ,
280+ ] = $ this ->convertImportedTime ($ deactivateTimeFields , $ data , startTime: $ startTime , errorMessage: $ errorMessage );
235281 if ($ errorMessage ) {
236282 return false ;
237283 }
@@ -247,11 +293,11 @@ public function importContestData(mixed $data, ?string &$errorMessage = null, ?s
247293 ->setExternalid ($ contest ->getShortname ())
248294 ->setWarningMessage ($ data ['warning_message ' ] ?? $ data ['warning-message ' ] ?? null )
249295 ->setStarttimeString (date_format ($ startTime , 'Y-m-d H:i:s e ' ))
250- ->setActivatetimeString (date_format ($ activateTime , 'Y-m-d H:i:s e ' ))
296+ ->setActivatetimeString ($ activateTimeIsRelative ? $ activateRelativeTime : date_format ($ activateTime , 'Y-m-d H:i:s e ' ))
251297 ->setEndtimeString (sprintf ('+%s ' , $ data ['duration ' ]))
252298 ->setPublic ($ data ['public ' ] ?? true );
253299 if ($ deactivateTime ) {
254- $ contest ->setDeactivatetimeString (date_format ($ deactivateTime , 'Y-m-d H:i:s e ' ));
300+ $ contest ->setDeactivatetimeString ($ deactivateTimeIsRelative ? $ deactivateRelativeTime : date_format ($ deactivateTime , 'Y-m-d H:i:s e ' ));
255301 }
256302
257303 // Get all visible categories. For now, we assume these are the ones getting awards
0 commit comments