@@ -503,63 +503,157 @@ function api_format_date($time, $format = null, $language = null)
503
503
}
504
504
505
505
/**
506
- * Returns the difference between the current date (date(now)) with the parameter
507
- * $date in a string format like "2 days ago, 1 hour ago".
508
- * You can use it like this:
509
- * Display::dateToStringAgoAndLongDate($dateInUtc);.
506
+ * Return a Westsworld\TimeAgo translation instance based on Chamilo's isocode.
507
+ * Tolerates custom isocodes like "pl_polish2", "es_spanish", etc.
510
508
*
511
- * @param string|DateTime $date Result of a date function in this format -> date('Y-m-d H:i:s', time());
512
- * @param string $timeZone
513
- * @param bool $returnDateDifference
514
- *
515
- * @return string
516
- *
517
- * @author Julio Montoya
509
+ * @return object Instance of Westsworld\TimeAgo\Translations\*
518
510
*/
519
- function date_to_str_ago ( $ date , $ timeZone = ' UTC ' , $ returnDateDifference = false )
511
+ function timeago_resolve_language (? string $ iso )
520
512
{
521
- if ('0000-00-00 00:00:00 ' === $ date ) {
522
- return '' ;
513
+ // Normalize ISO (snake_case, lowercase)
514
+ $ iso = $ iso ?: api_get_language_isocode ();
515
+ $ norm = strtolower (str_replace ('- ' , '_ ' , trim ($ iso )));
516
+
517
+ // Candidates: full, parent chain (if available), then base (first 2 letters)
518
+ $ candidates = [$ norm ];
519
+
520
+ if (class_exists ('SubLanguageManager ' )) {
521
+ $ tmp = $ iso ;
522
+ // SubLanguageManager returns parents like "en_US"; normalize to snake_case
523
+ while (!empty ($ parent = SubLanguageManager::getParentLocale ($ tmp ))) {
524
+ $ candidates [] = strtolower (str_replace ('- ' , '_ ' , $ parent ));
525
+ $ tmp = $ parent ;
526
+ }
523
527
}
524
528
525
- $ getOldTimezone = api_get_timezone ();
526
- $ isoCode = api_get_language_isocode ();
527
- if ('pt ' === $ isoCode ) {
528
- $ isoCode = 'pt-BR ' ;
529
+ if (preg_match ('/^[a-z]{2}/ ' , $ norm , $ m )) {
530
+ $ base = $ m [0 ]; // e.g. "pl" from "pl_polish2", "es" from "es_spanish"
531
+ $ candidates [] = $ base ;
532
+ } else {
533
+ $ base = $ norm ;
534
+ }
535
+
536
+ // Map of classes supported by the library (key = snake_case, value = class suffix)
537
+ $ map = [
538
+ 'ar ' => 'Ar ' , 'bg ' => 'Bg ' , 'cs ' => 'Cs ' , 'da ' => 'Da ' ,
539
+ 'de ' => 'De ' , 'el ' => 'El ' , 'en ' => 'En ' , 'es ' => 'Es ' ,
540
+ 'fa ' => 'Fa ' , 'fi ' => 'Fi ' , 'fr ' => 'Fr ' , 'he ' => 'He ' ,
541
+ 'hr ' => 'Hr ' , 'hu ' => 'Hu ' , 'id ' => 'Id ' , 'it ' => 'It ' ,
542
+ 'ja ' => 'Ja ' , 'ko ' => 'Ko ' , 'nb ' => 'Nb ' , 'nl ' => 'Nl ' ,
543
+ 'no ' => 'No ' , 'pl ' => 'Pl ' ,
544
+ 'pt_br ' => 'Pt_BR ' , 'pt_pt ' => 'Pt_PT ' ,
545
+ 'ro ' => 'Ro ' , 'ru ' => 'Ru ' , 'sk ' => 'Sk ' , 'sr ' => 'Sr ' ,
546
+ 'sv ' => 'Sv ' , 'tr ' => 'Tr ' , 'uk ' => 'Uk ' , 'vi ' => 'Vi ' ,
547
+ 'zh_cn ' => 'Zh_CN ' , 'zh_tw ' => 'Zh_TW ' ,
548
+ 'pt ' => 'Pt_BR ' ,
549
+ 'zh ' => 'Zh_CN ' ,
550
+ 'nn ' => 'Nb ' ,
551
+ ];
552
+
553
+ // Try exact candidates first, then special rules, then the base
554
+ foreach ($ candidates as $ cand ) {
555
+ if (isset ($ map [$ cand ])) {
556
+ $ class = "Westsworld \\TimeAgo \\Translations \\{$ map [$ cand ]}" ;
557
+ if (class_exists ($ class )) {
558
+ return new $ class ();
559
+ }
560
+ }
561
+
562
+ // Special handling when the candidate is an ambiguous base
563
+ if ($ cand === 'pt ' ) {
564
+ foreach (['Pt_BR ' , 'Pt_PT ' ] as $ suf ) {
565
+ $ class = "Westsworld \\TimeAgo \\Translations \\$ suf " ;
566
+ if (class_exists ($ class )) {
567
+ return new $ class ();
568
+ }
569
+ }
570
+ }
571
+ if ($ cand === 'zh ' ) {
572
+ foreach (['Zh_CN ' , 'Zh_TW ' ] as $ suf ) {
573
+ $ class = "Westsworld \\TimeAgo \\Translations \\$ suf " ;
574
+ if (class_exists ($ class )) {
575
+ return new $ class ();
576
+ }
577
+ }
578
+ }
579
+ if ($ cand === 'nn ' ) {
580
+ foreach (['Nb ' , 'No ' ] as $ suf ) {
581
+ $ class = "Westsworld \\TimeAgo \\Translations \\$ suf " ;
582
+ if (class_exists ($ class )) {
583
+ return new $ class ();
584
+ }
585
+ }
586
+ }
529
587
}
530
- if ('fr_FR ' === $ isoCode ) {
531
- $ isoCode = 'Fr ' ;
588
+
589
+ // Last attempt: try base directly if it has a mapping
590
+ if (isset ($ map [$ base ])) {
591
+ $ class = "Westsworld \\TimeAgo \\Translations \\{$ map [$ base ]}" ;
592
+ if (class_exists ($ class )) {
593
+ return new $ class ();
594
+ }
532
595
}
533
- $ isoCode = ucfirst ($ isoCode );
534
- $ class = "Westsworld\TimeAgo\Translations \\" .$ isoCode ;
535
- if (class_exists ($ class )) {
536
- $ language = new $ class ();
537
- } else {
538
- $ language = new Westsworld \TimeAgo \Translations \En ();
596
+
597
+ // Final fallback: English
598
+ return new Westsworld \TimeAgo \Translations \En ();
599
+ }
600
+
601
+ /**
602
+ * Time-ago function with proper locale resolution (including custom isocodes)
603
+ * and consistent timezone handling.
604
+ */
605
+ function date_to_str_ago ($ date , $ timeZone = null , $ returnDateDifference = false )
606
+ {
607
+ if (empty ($ date ) || '0000-00-00 00:00:00 ' === $ date ) {
608
+ return '' ;
539
609
}
540
610
541
- $ timeAgo = new TimeAgo ($ language );
611
+ // Resolve timezone: prefer parameter, otherwise user/platform timezone
612
+ $ tz = $ timeZone ?: api_get_timezone ();
613
+
614
+ // Resolve language for TimeAgo (tolerant to pl_polish2, es_spanish, etc.)
615
+ $ language = timeago_resolve_language (api_get_language_isocode ());
616
+ $ timeAgo = new TimeAgo ($ language );
617
+
618
+ // Normalize $date to DateTime in the same timezone as "now"
542
619
if (!($ date instanceof DateTime)) {
543
- $ date = api_get_utc_datetime ($ date , null , true );
620
+ if (is_numeric ($ date )) {
621
+ // Timestamp: create from UTC epoch and then set target TZ
622
+ $ dateObj = new DateTime ('@ ' .(int ) $ date );
623
+ $ dateObj ->setTimezone (new DateTimeZone ($ tz ));
624
+ $ date = $ dateObj ;
625
+ } else {
626
+ // Assume DB string is UTC, then convert to target TZ
627
+ $ date = new DateTime ($ date , new DateTimeZone ('UTC ' ));
628
+ $ date ->setTimezone (new DateTimeZone ($ tz ));
629
+ }
630
+ } else {
631
+ // Ensure provided DateTime uses the target TZ
632
+ $ date ->setTimezone (new DateTimeZone ($ tz ));
544
633
}
545
634
546
- $ value = $ timeAgo ->inWords ($ date );
547
- date_default_timezone_set ($ getOldTimezone );
635
+ // Ensure the library computes "now" in the same TZ
636
+ $ oldTz = date_default_timezone_get ();
637
+ date_default_timezone_set ($ tz );
548
638
549
639
if ($ returnDateDifference ) {
550
- $ now = new DateTime ('now ' , $ date ->getTimezone ());
551
- $ value = $ date ->diff ($ now );
640
+ $ now = new DateTime ('now ' , new DateTimeZone ($ tz ));
641
+ $ diff = $ date ->diff ($ now );
642
+ date_default_timezone_set ($ oldTz );
552
643
553
644
return [
554
- 'years ' => $ value ->y ,
555
- 'months ' => $ value ->m ,
556
- 'days ' => $ value ->d ,
557
- 'hours ' => $ value ->h ,
558
- 'minutes ' => $ value ->i ,
559
- 'seconds ' => $ value ->s ,
645
+ 'years ' => $ diff ->y ,
646
+ 'months ' => $ diff ->m ,
647
+ 'days ' => $ diff ->d ,
648
+ 'hours ' => $ diff ->h ,
649
+ 'minutes ' => $ diff ->i ,
650
+ 'seconds ' => $ diff ->s ,
560
651
];
561
652
}
562
653
654
+ $ value = $ timeAgo ->inWords ($ date );
655
+ date_default_timezone_set ($ oldTz );
656
+
563
657
return $ value ;
564
658
}
565
659
0 commit comments