mirror of
https://github.com/getformwork/formwork.git
synced 2025-01-17 21:49:04 +01:00
Merge pull request #62 from getformwork/feature/improved-date-formats
Improve date formats handling
This commit is contained in:
commit
c208789b43
2
admin/assets/js/app.min.js
vendored
2
admin/assets/js/app.min.js
vendored
File diff suppressed because one or more lines are too long
@ -2,11 +2,19 @@ import Utils from './utils';
|
||||
|
||||
export default function DatePicker(input, options) {
|
||||
var defaults = {
|
||||
dayLabels: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
||||
monthLabels: ['January', 'February', 'March', 'April', 'May', 'June', 'July' ,'August', 'September', 'October', 'November', 'December'],
|
||||
weekStarts: 0,
|
||||
todayLabel: 'Today',
|
||||
format: 'YYYY-MM-DD'
|
||||
format: 'YYYY-MM-DD',
|
||||
labels: {
|
||||
today: 'Today',
|
||||
weekdays: {
|
||||
long: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
||||
short: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
||||
},
|
||||
months: {
|
||||
long: ['January', 'February', 'March', 'April', 'May', 'June', 'July' ,'August', 'September', 'October', 'November', 'December'],
|
||||
short: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var today = new Date();
|
||||
@ -86,9 +94,6 @@ export default function DatePicker(input, options) {
|
||||
// Return x mod y (always rounded downwards, differs from x % y which is the remainder)
|
||||
return x - y * Math.floor(x / y);
|
||||
},
|
||||
pad: function (num) {
|
||||
return num.toString().length === 1 ? '0' + num : num;
|
||||
},
|
||||
isValidDate: function (date) {
|
||||
return date && !isNaN(Date.parse(date));
|
||||
},
|
||||
@ -98,26 +103,115 @@ export default function DatePicker(input, options) {
|
||||
daysInMonth: function (month, year) {
|
||||
return month === 1 && this.isLeapYear(year) ? 29 : this._daysInMonth[month];
|
||||
},
|
||||
formatDateTime: function (date) {
|
||||
var format = options.format;
|
||||
var year = date.getFullYear();
|
||||
var month = date.getMonth() + 1;
|
||||
weekStart: function (date, firstDay) {
|
||||
var day = date.getDate();
|
||||
var hours = date.getHours();
|
||||
var minutes = date.getMinutes();
|
||||
var seconds = date.getSeconds();
|
||||
var am = hours < 12;
|
||||
if (format.indexOf('a') > -1) {
|
||||
hours = dateHelpers.mod(hours, 12) > 0 ? dateHelpers.mod(hours, 12) : 12;
|
||||
if (typeof firstDay === 'undefined') {
|
||||
firstDay = options.weekStarts;
|
||||
}
|
||||
return format.replace('YYYY', year)
|
||||
.replace('YY', year.toString().substr(-2))
|
||||
.replace('MM', dateHelpers.pad(month))
|
||||
.replace('DD', dateHelpers.pad(day))
|
||||
.replace('hh', dateHelpers.pad(hours))
|
||||
.replace('mm', dateHelpers.pad(minutes))
|
||||
.replace('ss', dateHelpers.pad(seconds))
|
||||
.replace('a', am ? 'AM' : 'PM');
|
||||
day -= this.mod(date.getDay() - firstDay, 7);
|
||||
return new Date(date.getFullYear(), date.getMonth(), day);
|
||||
},
|
||||
weekNumberingYear: function (date) {
|
||||
var year = date.getFullYear();
|
||||
var thisYearFirstWeekStart = this.weekStart(new Date(year, 0, 4), 1);
|
||||
var nextYearFirstWeekStart = this.weekStart(new Date(year + 1, 0, 4), 1);
|
||||
if (date.getTime() >= nextYearFirstWeekStart.getTime()) {
|
||||
return year + 1;
|
||||
} else if (date.getTime() >= thisYearFirstWeekStart.getTime()) {
|
||||
return year;
|
||||
}
|
||||
return year - 1;
|
||||
},
|
||||
weekOfYear: function (date) {
|
||||
var weekNumberingYear = this.weekNumberingYear(date);
|
||||
var firstWeekStart = this.weekStart(new Date(weekNumberingYear, 0, 4), 1);
|
||||
var weekStart = this.weekStart(date, 1);
|
||||
return Math.round((weekStart.getTime() - firstWeekStart.getTime()) / 604800000) + 1;
|
||||
},
|
||||
formatDateTime: function (date, format) {
|
||||
var regex = /\[([^\]]*)\]|[YR]{4}|uuu|[YR]{2}|[MD]{1,4}|[WHhms]{1,2}|[AaZz]/g;
|
||||
|
||||
if (typeof format === 'undefined') {
|
||||
format = options.format;
|
||||
}
|
||||
|
||||
function pad(num, length) {
|
||||
var result = num.toString();
|
||||
while (result.length < length) {
|
||||
result = '0' + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function splitTimezoneOffset(offset) {
|
||||
// Note that the offset returned by Date.getTimezoneOffset()
|
||||
// is positive if behind UTC and negative if ahead UTC
|
||||
var sign = offset > 0 ? '-' : '+';
|
||||
var hours = Math.floor(Math.abs(offset) / 60);
|
||||
var minutes = Math.abs(offset) % 60;
|
||||
return [sign + pad(hours, 2), pad(minutes, 2)];
|
||||
}
|
||||
|
||||
return format.replace(regex, function (match, $1) {
|
||||
switch (match) {
|
||||
case 'YY':
|
||||
return date.getFullYear().toString().substr(-2);
|
||||
case 'YYYY':
|
||||
return date.getFullYear();
|
||||
case 'M':
|
||||
return date.getMonth() + 1;
|
||||
case 'MM':
|
||||
return pad(date.getMonth() + 1, 2);
|
||||
case 'MMM':
|
||||
return options.labels.months.short[date.getMonth()];
|
||||
case 'MMMM':
|
||||
return options.labels.months.long[date.getMonth()];
|
||||
case 'D':
|
||||
return date.getDate();
|
||||
case 'DD':
|
||||
return pad(date.getDate(), 2);
|
||||
case 'DDD':
|
||||
return options.labels.weekdays.short[dateHelpers.mod(date.getDay() + options.weekStarts, 7)];
|
||||
case 'DDDD':
|
||||
return options.labels.weekdays.long[dateHelpers.mod(date.getDay() + options.weekStarts, 7)];
|
||||
case 'W':
|
||||
return dateHelpers.weekOfYear(date);
|
||||
case 'WW':
|
||||
return pad(dateHelpers.weekOfYear(date), 2);
|
||||
case 'RR':
|
||||
return dateHelpers.weekNumberingYear(date).toString().substr(-2);
|
||||
case 'RRRR':
|
||||
return dateHelpers.weekNumberingYear(date);
|
||||
case 'H':
|
||||
return dateHelpers.mod(date.getHours(), 12) || 12;
|
||||
case 'HH':
|
||||
return pad(dateHelpers.mod(date.getHours(), 12) || 12, 2);
|
||||
case 'h':
|
||||
return date.getHours();
|
||||
case 'hh':
|
||||
return pad(date.getHours(), 2);
|
||||
case 'm':
|
||||
return date.getMinutes();
|
||||
case 'mm':
|
||||
return pad(date.getMinutes(), 2);
|
||||
case 's':
|
||||
return date.getSeconds();
|
||||
case 'ss':
|
||||
return pad(date.getSeconds(), 2);
|
||||
case 'uuu':
|
||||
return pad(date.getMilliseconds(), 3);
|
||||
case 'A':
|
||||
return date.getHours() < 12 ? 'AM' : 'PM';
|
||||
case 'a':
|
||||
return date.getHours() < 12 ? 'am' : 'pm';
|
||||
case 'Z':
|
||||
return splitTimezoneOffset(date.getTimezoneOffset()).join(':');
|
||||
case 'z':
|
||||
return splitTimezoneOffset(date.getTimezoneOffset()).join('');
|
||||
default:
|
||||
return $1 || match;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -230,7 +324,7 @@ export default function DatePicker(input, options) {
|
||||
function generateCalendar() {
|
||||
calendar = document.createElement('div');
|
||||
calendar.className = 'calendar';
|
||||
calendar.innerHTML = '<div class="calendar-buttons"><button type="button" class="prevMonth"><i class="i-chevron-left"></i></button><button class="currentMonth">' + options.todayLabel + '</button><button type="button" class="nextMonth"><i class="i-chevron-right"></i></button></div><div class="calendar-separator"></div><table class="calendar-table"></table>';
|
||||
calendar.innerHTML = '<div class="calendar-buttons"><button type="button" class="prevMonth"><i class="i-chevron-left"></i></button><button class="currentMonth">' + options.labels.today + '</button><button type="button" class="nextMonth"><i class="i-chevron-right"></i></button></div><div class="calendar-separator"></div><table class="calendar-table"></table>';
|
||||
document.body.appendChild(calendar);
|
||||
|
||||
$('.currentMonth', calendar).addEventListener('mousedown', function (event) {
|
||||
@ -272,7 +366,7 @@ export default function DatePicker(input, options) {
|
||||
var num = 1;
|
||||
var firstDay = new Date(year, month, 1).getDay();
|
||||
var monthLength = dateHelpers.daysInMonth(month, year);
|
||||
var monthName = options.monthLabels[month];
|
||||
var monthName = options.labels.months.long[month];
|
||||
var start = dateHelpers.mod(firstDay - options.weekStarts, 7);
|
||||
var html = '';
|
||||
html += '<tr><th class="calendar-header" colspan="7">';
|
||||
@ -281,7 +375,7 @@ export default function DatePicker(input, options) {
|
||||
html += '<tr>';
|
||||
for (i = 0; i < 7; i++ ){
|
||||
html += '<td class="calendar-header-day">';
|
||||
html += options.dayLabels[dateHelpers.mod(i + options.weekStarts, 7)];
|
||||
html += options.labels.weekdays.short[dateHelpers.mod(i + options.weekStarts, 7)];
|
||||
html += '</td>';
|
||||
}
|
||||
html += '</tr><tr>';
|
||||
|
@ -6,6 +6,7 @@ use Formwork\Admin\Admin;
|
||||
use Formwork\Admin\AdminTrait;
|
||||
use Formwork\Admin\Security\CSRFToken;
|
||||
use Formwork\Admin\Users\User;
|
||||
use Formwork\Admin\Utils\DateFormats;
|
||||
use Formwork\Admin\View\View;
|
||||
use Formwork\Core\Formwork;
|
||||
use Formwork\Core\Site;
|
||||
@ -68,14 +69,13 @@ abstract class AbstractController
|
||||
'appConfig' => JSON::encode([
|
||||
'baseUri' => $this->panelUri(),
|
||||
'DatePicker' => [
|
||||
'dayLabels' => $this->label('date.weekdays.short'),
|
||||
'monthLabels' => $this->label('date.months.long'),
|
||||
'weekStarts' => $this->option('date.week_starts'),
|
||||
'todayLabel' => $this->label('date.today'),
|
||||
'format' => strtr(
|
||||
$this->option('date.format'),
|
||||
['Y' => 'YYYY', 'm' => 'MM', 'd' => 'DD', 'H' => 'hh', 'i' => 'mm', 's' => 'ss', 'A' => 'a']
|
||||
)
|
||||
'weekStarts' => $this->option('date.week_starts'),
|
||||
'format' => DateFormats::formatToPattern(Formwork::instance()->option('date.format')),
|
||||
'labels' => [
|
||||
'today' => $this->label('date.today'),
|
||||
'weekdays' => ['long' => $this->label('date.weekdays.long'), 'short' => $this->label('date.weekdays.short')],
|
||||
'months' => ['long' => $this->label('date.months.long'), 'short' => $this->label('date.months.short')]
|
||||
]
|
||||
]
|
||||
])
|
||||
];
|
||||
|
@ -2,8 +2,45 @@
|
||||
|
||||
namespace Formwork\Admin\Utils;
|
||||
|
||||
use Formwork\Admin\Admin;
|
||||
use DateTime;
|
||||
|
||||
class DateFormats
|
||||
{
|
||||
/**
|
||||
* Characters used in formats accepted by date()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected const DATE_FORMAT_CHARACTERS = 'AaBcDdeFgGHhIijlLMmnNoOpPrsSTtUuvWwyYzZ';
|
||||
|
||||
/**
|
||||
* Regex used to parse formats accepted by date()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected const DATE_FORMAT_REGEX = '/((?:\\\\[A-Za-z])+)|[' . self::DATE_FORMAT_CHARACTERS . ']/';
|
||||
|
||||
/**
|
||||
* Regex used to parse date patterns like 'DD/MM/YYYY hh:mm:ss'
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected const PATTERN_REGEX = '/(?:\[([^\]]+)\])|[YR]{4}|uuu|[YR]{2}|[MD]{1,4}|[WHhms]{1,2}|[AaZz]/';
|
||||
|
||||
/**
|
||||
* Array used to translate pattern tokens to their date() format counterparts
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected const PATTERN_TO_DATE_FORMAT = [
|
||||
'YY' => 'y', 'YYYY' => 'Y', 'M' => 'n', 'MM' => 'm', 'MMM' => 'M', 'MMMM' => 'F',
|
||||
'D' => 'j', 'DD' => 'd', 'DDD' => 'D', 'DDDD' => 'l', 'W' => 'W', 'WW' => 'W',
|
||||
'RR' => 'o', 'RRRR' => 'o', 'H' => 'g', 'HH' => 'h', 'h' => 'G', 'hh' => 'H',
|
||||
'm' => 'i', 'mm' => 'i', 's' => 's', 'ss' => 's', 'uuu' => 'v', 'A' => 'A',
|
||||
'a' => 'a', 'Z' => 'P', 'z' => 'O'
|
||||
];
|
||||
|
||||
/**
|
||||
* Return common date formats
|
||||
*/
|
||||
@ -11,7 +48,7 @@ class DateFormats
|
||||
{
|
||||
$formats = [];
|
||||
foreach (['d/m/Y', 'm/d/Y', 'Y-m-d', 'd-m-Y'] as $format) {
|
||||
$formats[$format] = date($format) . ' (' . $format . ')';
|
||||
$formats[$format] = date($format) . ' (' . static::formatToPattern($format) . ')';
|
||||
}
|
||||
return $formats;
|
||||
}
|
||||
@ -23,7 +60,7 @@ class DateFormats
|
||||
{
|
||||
$formats = [];
|
||||
foreach (['H:i', 'h:i A'] as $format) {
|
||||
$formats[$format] = date($format) . ' (' . $format . ')';
|
||||
$formats[$format] = date($format) . ' (' . static::formatToPattern($format) . ')';
|
||||
}
|
||||
return $formats;
|
||||
}
|
||||
@ -39,4 +76,75 @@ class DateFormats
|
||||
}
|
||||
return $timezones;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a format accepted by date() to its corresponding pattern, e.g. the format 'd/m/Y \a\t h:i:s'
|
||||
* is converted to 'DD/MM/YYYY [at] hh:mm:ss'
|
||||
*/
|
||||
public static function formatToPattern(string $format): string
|
||||
{
|
||||
$map = array_flip(self::PATTERN_TO_DATE_FORMAT);
|
||||
return preg_replace_callback(
|
||||
self::DATE_FORMAT_REGEX,
|
||||
static function (array $matches) use ($map): string {
|
||||
return isset($matches[1])
|
||||
? '[' . str_replace('\\', '', $matches[1]) . ']'
|
||||
: ($map[$matches[0]] ?? $matches[0]);
|
||||
},
|
||||
$format
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a pattern to its corresponding format accepted by date(), e.g. the format
|
||||
* 'DDDD DD MMMM YYYY [at] HH:mm:ss A [o\' clock]' is converted to 'l d F Y \a\t h:i:s A \o\' \c\l\o\c\k',
|
||||
* where brackets are used to escape literal string portions
|
||||
*/
|
||||
public static function patternToFormat(string $pattern): string
|
||||
{
|
||||
return preg_replace_callback(
|
||||
self::PATTERN_REGEX,
|
||||
static function (array $matches): string {
|
||||
return isset($matches[1])
|
||||
? addcslashes($matches[1], 'A..Za..z')
|
||||
: (self::PATTERN_TO_DATE_FORMAT[$matches[0]] ?? $matches[0]);
|
||||
},
|
||||
$pattern
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a DateTime object using the current translation for weekdays and months
|
||||
*/
|
||||
public static function formatDateTime(DateTime $dateTime, string $format): string
|
||||
{
|
||||
return preg_replace_callback(
|
||||
self::DATE_FORMAT_REGEX,
|
||||
static function (array $matches) use ($dateTime): string {
|
||||
switch ($matches[0]) {
|
||||
case 'M':
|
||||
return Admin::instance()->label('date.months.short')[$dateTime->format('n') - 1];
|
||||
case 'F':
|
||||
return Admin::instance()->label('date.months.long')[$dateTime->format('n') - 1];
|
||||
case 'D':
|
||||
return Admin::instance()->label('date.weekdays.short')[$dateTime->format('w')];
|
||||
case 'l':
|
||||
return Admin::instance()->label('date.weekdays.long')[$dateTime->format('w')];
|
||||
case 'r':
|
||||
return self::formatDateTime($dateTime, DateTime::RFC2822);
|
||||
default:
|
||||
return $dateTime->format($matches[1] ?? $matches[0]);
|
||||
}
|
||||
},
|
||||
$format
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The same as self::formatDateTime() but takes a timestamp instead of a DateTime object
|
||||
*/
|
||||
public static function formatTimestamp(int $timestamp, string $format): string
|
||||
{
|
||||
return static::formatDateTime(new DateTime('@' . $timestamp), $format);
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ dashboard.welcome: Welcome
|
||||
date.months.long: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
|
||||
date.months.short: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
date.today: Today
|
||||
date.weekdays.long: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
|
||||
date.weekdays.short: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
||||
errors.action.report-to-github: Report an issue to GitHub
|
||||
errors.action.return-to-dashboard: Return to Dashboard
|
||||
|
@ -18,6 +18,7 @@ dashboard.welcome: Bienvenue
|
||||
date.months.long: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre']
|
||||
date.months.short: ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jun', 'Juil', 'Aou', 'Sep', 'Oct', 'Nov', 'Déc']
|
||||
date.today: Aujourd’hui
|
||||
date.weekdays.long: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi']
|
||||
date.weekdays.short: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam']
|
||||
errors.action.report-to-github: Signaler le problème sur GitHub
|
||||
errors.action.return-to-dashboard: Retour au tableau de bord
|
||||
|
@ -18,6 +18,7 @@ dashboard.welcome: Benvenuto/a
|
||||
date.months.long: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre']
|
||||
date.months.short: ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic']
|
||||
date.today: Oggi
|
||||
date.weekdays.long: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato']
|
||||
date.weekdays.short: ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab']
|
||||
errors.action.report-to-github: Segnala un problema su GitHub
|
||||
errors.action.return-to-dashboard: Torna al Riepilogo
|
||||
|
@ -18,6 +18,7 @@ dashboard.welcome: Добро пожаловать
|
||||
date.months.long: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октября', 'Ноябрь', 'Декабрь']
|
||||
date.months.short: ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек']
|
||||
date.today: Сегодня
|
||||
date.weekdays.long: ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота']
|
||||
date.weekdays.short: ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб']
|
||||
errors.action.report-to-github: Сообщить о проблеме на GitHub
|
||||
errors.action.return-to-dashboard: Вернуться к панели управления
|
||||
@ -91,7 +92,7 @@ options.system.images.png-compression-level: PNG Уровень сжатия
|
||||
options.system.languages: Языки
|
||||
options.system.languages.available-languages: Доступные Языки
|
||||
options.system.languages.available-languages.no-languages: Нет Языков
|
||||
options.system.languages.preferred-language: Использовать предпочитаемый язык браузера
|
||||
options.system.languages.preferred-language: Использовать предпочитаемый язык браузера
|
||||
options.system.languages.preferred-language.disabled: Выключить
|
||||
options.system.languages.preferred-language.enabled: Включить
|
||||
options.updated: Опции обновляются
|
||||
|
Loading…
x
Reference in New Issue
Block a user