1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-03-05 13:20:20 -05:00

New: Option to specify timezone for formatting times in the UI

This commit is contained in:
Audionut
2025-10-22 14:07:34 +10:00
committed by GitHub
parent 37dfad11f2
commit 550cf8d399
16 changed files with 321 additions and 35 deletions

View File

@@ -1,5 +1,4 @@
import classNames from 'classnames';
import moment from 'moment';
import React, { useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
import { useQueueItemForEpisode } from 'Activity/Queue/Details/QueueDetailsProvider';
@@ -15,6 +14,7 @@ import useEpisodeFile from 'EpisodeFile/useEpisodeFile';
import { icons, kinds } from 'Helpers/Props';
import useSeries from 'Series/useSeries';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import { convertToTimezone } from 'Utilities/Date/convertToTimezone';
import formatTime from 'Utilities/Date/formatTime';
import padNumber from 'Utilities/Number/padNumber';
import translate from 'Utilities/String/translate';
@@ -58,9 +58,8 @@ function AgendaEvent(props: AgendaEventProps) {
const series = useSeries(seriesId)!;
const episodeFile = useEpisodeFile(episodeFileId);
const queueItem = useQueueItemForEpisode(id);
const { timeFormat, longDateFormat, enableColorImpairedMode } = useSelector(
createUISettingsSelector()
);
const { timeFormat, longDateFormat, enableColorImpairedMode, timeZone } =
useSelector(createUISettingsSelector());
const {
showEpisodeInformation,
@@ -71,8 +70,11 @@ function AgendaEvent(props: AgendaEventProps) {
const [isDetailsModalOpen, setIsDetailsModalOpen] = useState(false);
const startTime = moment(airDateUtc);
const endTime = moment(airDateUtc).add(series.runtime, 'minutes');
const startTime = convertToTimezone(airDateUtc, timeZone);
const endTime = convertToTimezone(airDateUtc, timeZone).add(
series.runtime,
'minutes'
);
const downloading = !!(queueItem || grabbed);
const isMonitored = series.monitored && monitored;
const statusStyle = getStatusStyle(
@@ -110,9 +112,10 @@ function AgendaEvent(props: AgendaEventProps) {
)}
>
<div className={styles.time}>
{formatTime(airDateUtc, timeFormat)} -{' '}
{formatTime(airDateUtc, timeFormat, { timeZone })} -{' '}
{formatTime(endTime.toISOString(), timeFormat, {
includeMinuteZero: true,
timeZone,
})}
</div>

View File

@@ -1,5 +1,4 @@
import classNames from 'classnames';
import moment from 'moment';
import React, { useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
import { useQueueItemForEpisode } from 'Activity/Queue/Details/QueueDetailsProvider';
@@ -14,6 +13,7 @@ import useEpisodeFile from 'EpisodeFile/useEpisodeFile';
import { icons, kinds } from 'Helpers/Props';
import useSeries from 'Series/useSeries';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import { convertToTimezone } from 'Utilities/Date/convertToTimezone';
import formatTime from 'Utilities/Date/formatTime';
import padNumber from 'Utilities/Number/padNumber';
import translate from 'Utilities/String/translate';
@@ -60,7 +60,7 @@ function CalendarEvent(props: CalendarEventProps) {
const episodeFile = useEpisodeFile(episodeFileId);
const queueItem = useQueueItemForEpisode(id);
const { timeFormat, enableColorImpairedMode } = useSelector(
const { timeFormat, enableColorImpairedMode, timeZone } = useSelector(
createUISettingsSelector()
);
@@ -88,8 +88,11 @@ function CalendarEvent(props: CalendarEventProps) {
return null;
}
const startTime = moment(airDateUtc);
const endTime = moment(airDateUtc).add(series.runtime, 'minutes');
const startTime = convertToTimezone(airDateUtc, timeZone);
const endTime = convertToTimezone(airDateUtc, timeZone).add(
series.runtime,
'minutes'
);
const isDownloading = !!(queueItem || grabbed);
const isMonitored = series.monitored && monitored;
const statusStyle = getStatusStyle(
@@ -217,9 +220,10 @@ function CalendarEvent(props: CalendarEventProps) {
) : null}
<div className={styles.airTime}>
{formatTime(airDateUtc, timeFormat)} -{' '}
{formatTime(airDateUtc, timeFormat, { timeZone })} -{' '}
{formatTime(endTime.toISOString(), timeFormat, {
includeMinuteZero: true,
timeZone,
})}
</div>
</div>

View File

@@ -1,5 +1,4 @@
import classNames from 'classnames';
import moment from 'moment';
import React, { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useIsDownloadingEpisodes } from 'Activity/Queue/Details/QueueDetailsProvider';
@@ -12,6 +11,7 @@ import { icons, kinds } from 'Helpers/Props';
import useSeries from 'Series/useSeries';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import { CalendarItem } from 'typings/Calendar';
import { convertToTimezone } from 'Utilities/Date/convertToTimezone';
import formatTime from 'Utilities/Date/formatTime';
import padNumber from 'Utilities/Number/padNumber';
import translate from 'Utilities/String/translate';
@@ -34,7 +34,7 @@ function CalendarEventGroup({
const isDownloading = useIsDownloadingEpisodes(episodeIds);
const series = useSeries(seriesId)!;
const { timeFormat, enableColorImpairedMode } = useSelector(
const { timeFormat, enableColorImpairedMode, timeZone } = useSelector(
createUISettingsSelector()
);
@@ -46,8 +46,11 @@ function CalendarEventGroup({
const firstEpisode = events[0];
const lastEpisode = events[events.length - 1];
const airDateUtc = firstEpisode.airDateUtc;
const startTime = moment(airDateUtc);
const endTime = moment(lastEpisode.airDateUtc).add(series.runtime, 'minutes');
const startTime = convertToTimezone(airDateUtc, timeZone);
const endTime = convertToTimezone(lastEpisode.airDateUtc, timeZone).add(
series.runtime,
'minutes'
);
const seasonNumber = firstEpisode.seasonNumber;
const { allDownloaded, anyGrabbed, anyMonitored, allAbsoluteEpisodeNumbers } =
@@ -194,9 +197,10 @@ function CalendarEventGroup({
<div className={styles.airingInfo}>
<div className={styles.airTime}>
{formatTime(airDateUtc, timeFormat)} -{' '}
{formatTime(airDateUtc, timeFormat, { timeZone })} -{' '}
{formatTime(endTime.toISOString(), timeFormat, {
includeMinuteZero: true,
timeZone,
})}
</div>

View File

@@ -116,7 +116,7 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
onGrabPress,
} = props;
const { longDateFormat, timeFormat } = useSelector(
const { longDateFormat, timeFormat, timeZone } = useSelector(
createUISettingsSelector()
);
@@ -174,6 +174,7 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
className={styles.age}
title={formatDateTime(publishDate, longDateFormat, timeFormat, {
includeSeconds: true,
timeZone,
})}
>
{formatAge(age, ageHours, ageMinutes)}

View File

@@ -21,6 +21,7 @@ import createLanguagesSelector from 'Store/Selectors/createLanguagesSelector';
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
import themes from 'Styles/Themes';
import { InputChanged } from 'typings/inputs';
import timeZoneOptions from 'Utilities/Date/timeZoneOptions';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
@@ -218,6 +219,18 @@ function UISettings() {
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('TimeZone')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="timeZone"
values={timeZoneOptions}
onChange={handleInputChange}
{...settings.timeZone}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('ShowRelativeDates')}</FormLabel>
<FormInputGroup

View File

@@ -0,0 +1,26 @@
import moment from 'moment-timezone';
export const convertToTimezone = (
date: moment.MomentInput,
timeZone: string
) => {
if (!date) {
return moment();
}
if (!timeZone) {
return moment(date);
}
try {
return moment.tz(date, timeZone);
} catch (error) {
console.warn(
`Error converting to timezone ${timeZone}. Using system timezone.`,
error
);
return moment(date);
}
};
export default convertToTimezone;

View File

@@ -1,11 +1,15 @@
import moment, { MomentInput } from 'moment';
import moment from 'moment-timezone';
import translate from 'Utilities/String/translate';
import { convertToTimezone } from './convertToTimezone';
import formatTime from './formatTime';
import isToday from './isToday';
import isTomorrow from './isTomorrow';
import isYesterday from './isYesterday';
function getRelativeDay(date: MomentInput, includeRelativeDate: boolean) {
function getRelativeDay(
date: moment.MomentInput,
includeRelativeDate: boolean
) {
if (!includeRelativeDate) {
return '';
}
@@ -26,20 +30,23 @@ function getRelativeDay(date: MomentInput, includeRelativeDate: boolean) {
}
function formatDateTime(
date: MomentInput,
date: moment.MomentInput,
dateFormat: string,
timeFormat: string,
{ includeSeconds = false, includeRelativeDay = false } = {}
{ includeSeconds = false, includeRelativeDay = false, timeZone = '' } = {}
) {
if (!date) {
return '';
}
const relativeDay = getRelativeDay(date, includeRelativeDay);
const formattedDate = moment(date).format(dateFormat);
const formattedTime = formatTime(date, timeFormat, {
const dateTime = convertToTimezone(date, timeZone);
const relativeDay = getRelativeDay(dateTime, includeRelativeDay);
const formattedDate = dateTime.format(dateFormat);
const formattedTime = formatTime(dateTime, timeFormat, {
includeMinuteZero: true,
includeSeconds,
timeZone,
});
if (relativeDay) {

View File

@@ -1,15 +1,16 @@
import moment, { MomentInput } from 'moment';
import moment from 'moment-timezone';
import { convertToTimezone } from './convertToTimezone';
function formatTime(
date: MomentInput,
date: moment.MomentInput,
timeFormat: string,
{ includeMinuteZero = false, includeSeconds = false } = {}
{ includeMinuteZero = false, includeSeconds = false, timeZone = '' } = {}
) {
if (!date) {
return '';
}
const time = moment(date);
const time = convertToTimezone(date, timeZone);
if (includeSeconds) {
timeFormat = timeFormat.replace(/\(?:mm\)?/, ':mm:ss');

View File

@@ -1,10 +1,10 @@
import moment from 'moment';
import formatTime from 'Utilities/Date/formatTime';
import isInNextWeek from 'Utilities/Date/isInNextWeek';
import isToday from 'Utilities/Date/isToday';
import isTomorrow from 'Utilities/Date/isTomorrow';
import isYesterday from 'Utilities/Date/isYesterday';
import translate from 'Utilities/String/translate';
import { convertToTimezone } from './convertToTimezone';
import formatDateTime from './formatDateTime';
interface GetRelativeDateOptions {
@@ -12,6 +12,7 @@ interface GetRelativeDateOptions {
shortDateFormat: string;
showRelativeDates: boolean;
timeFormat?: string;
timeZone?: string;
includeSeconds?: boolean;
timeForToday?: boolean;
includeTime?: boolean;
@@ -22,6 +23,7 @@ function getRelativeDate({
shortDateFormat,
showRelativeDates,
timeFormat,
timeZone = '',
includeSeconds = false,
timeForToday = false,
includeTime = false,
@@ -41,6 +43,7 @@ function getRelativeDate({
? formatTime(date, timeFormat, {
includeMinuteZero: true,
includeSeconds,
timeZone,
})
: '';
@@ -49,7 +52,8 @@ function getRelativeDate({
}
if (!showRelativeDates) {
return moment(date).format(shortDateFormat);
const dateTime = convertToTimezone(date, timeZone);
return dateTime.format(shortDateFormat);
}
if (isYesterday(date)) {
@@ -69,14 +73,18 @@ function getRelativeDate({
}
if (isInNextWeek(date)) {
const day = moment(date).format('dddd');
const dateTime = convertToTimezone(date, timeZone);
const day = dateTime.format('dddd');
return includeTime ? translate('DayOfWeekAt', { day, time }) : day;
}
return includeTime && timeFormat
? formatDateTime(date, shortDateFormat, timeFormat, { includeSeconds })
: moment(date).format(shortDateFormat);
? formatDateTime(date, shortDateFormat, timeFormat, {
includeSeconds,
timeZone,
})
: convertToTimezone(date, timeZone).format(shortDateFormat);
}
export default getRelativeDate;

View File

@@ -0,0 +1,192 @@
import { EnhancedSelectInputValue } from 'Components/Form/Select/EnhancedSelectInput';
import translate from 'Utilities/String/translate';
export const timeZoneOptions: EnhancedSelectInputValue<string>[] = [
{
key: '',
get value() {
return translate('SystemDefault');
},
},
// UTC
{ key: 'UTC', value: 'UTC' },
// Africa (Major cities and unique timezones)
{ key: 'Africa/Abidjan', value: 'Africa/Abidjan' },
{ key: 'Africa/Algiers', value: 'Africa/Algiers' },
{ key: 'Africa/Cairo', value: 'Africa/Cairo' },
{ key: 'Africa/Casablanca', value: 'Africa/Casablanca' },
{ key: 'Africa/Johannesburg', value: 'Africa/Johannesburg' },
{ key: 'Africa/Lagos', value: 'Africa/Lagos' },
{ key: 'Africa/Nairobi', value: 'Africa/Nairobi' },
{ key: 'Africa/Tripoli', value: 'Africa/Tripoli' },
// America - North America (Major US/Canada zones)
{ key: 'America/New_York', value: 'America/New_York (Eastern)' },
{ key: 'America/Chicago', value: 'America/Chicago (Central)' },
{ key: 'America/Denver', value: 'America/Denver (Mountain)' },
{ key: 'America/Los_Angeles', value: 'America/Los_Angeles (Pacific)' },
{ key: 'America/Anchorage', value: 'America/Anchorage (Alaska)' },
{ key: 'America/Adak', value: 'America/Adak (Hawaii-Aleutian)' },
{ key: 'America/Phoenix', value: 'America/Phoenix (Arizona)' },
{ key: 'America/Toronto', value: 'America/Toronto' },
{ key: 'America/Vancouver', value: 'America/Vancouver' },
{ key: 'America/Halifax', value: 'America/Halifax' },
{ key: 'America/St_Johns', value: 'America/St_Johns (Newfoundland)' },
// America - Mexico
{ key: 'America/Mexico_City', value: 'America/Mexico_City' },
{ key: 'America/Cancun', value: 'America/Cancun' },
{ key: 'America/Tijuana', value: 'America/Tijuana' },
// America - Central America
{ key: 'America/Guatemala', value: 'America/Guatemala' },
{ key: 'America/Costa_Rica', value: 'America/Costa_Rica' },
{ key: 'America/Panama', value: 'America/Panama' },
// America - Caribbean
{ key: 'America/Havana', value: 'America/Havana' },
{ key: 'America/Jamaica', value: 'America/Jamaica' },
{ key: 'America/Puerto_Rico', value: 'America/Puerto_Rico' },
// America - South America
{ key: 'America/Bogota', value: 'America/Bogota' },
{ key: 'America/Caracas', value: 'America/Caracas' },
{ key: 'America/Guyana', value: 'America/Guyana' },
{ key: 'America/La_Paz', value: 'America/La_Paz' },
{ key: 'America/Lima', value: 'America/Lima' },
{ key: 'America/Santiago', value: 'America/Santiago' },
{ key: 'America/Asuncion', value: 'America/Asuncion' },
{ key: 'America/Montevideo', value: 'America/Montevideo' },
{
key: 'America/Argentina/Buenos_Aires',
value: 'America/Argentina/Buenos_Aires',
},
{ key: 'America/Sao_Paulo', value: 'America/Sao_Paulo' },
{ key: 'America/Manaus', value: 'America/Manaus' },
{ key: 'America/Fortaleza', value: 'America/Fortaleza' },
{ key: 'America/Noronha', value: 'America/Noronha' },
// Antarctica (Research stations)
{ key: 'Antarctica/McMurdo', value: 'Antarctica/McMurdo' },
{ key: 'Antarctica/Palmer', value: 'Antarctica/Palmer' },
// Arctic
{ key: 'Arctic/Longyearbyen', value: 'Arctic/Longyearbyen' },
// Asia - East Asia
{ key: 'Asia/Tokyo', value: 'Asia/Tokyo' },
{ key: 'Asia/Seoul', value: 'Asia/Seoul' },
{ key: 'Asia/Shanghai', value: 'Asia/Shanghai' },
{ key: 'Asia/Hong_Kong', value: 'Asia/Hong_Kong' },
{ key: 'Asia/Taipei', value: 'Asia/Taipei' },
{ key: 'Asia/Macau', value: 'Asia/Macau' },
// Asia - Southeast Asia
{ key: 'Asia/Singapore', value: 'Asia/Singapore' },
{ key: 'Asia/Kuala_Lumpur', value: 'Asia/Kuala_Lumpur' },
{ key: 'Asia/Jakarta', value: 'Asia/Jakarta' },
{ key: 'Asia/Manila', value: 'Asia/Manila' },
{ key: 'Asia/Bangkok', value: 'Asia/Bangkok' },
{ key: 'Asia/Ho_Chi_Minh', value: 'Asia/Ho_Chi_Minh' },
// Asia - South Asia
{ key: 'Asia/Kolkata', value: 'Asia/Kolkata' },
{ key: 'Asia/Dhaka', value: 'Asia/Dhaka' },
{ key: 'Asia/Karachi', value: 'Asia/Karachi' },
{ key: 'Asia/Kathmandu', value: 'Asia/Kathmandu' },
{ key: 'Asia/Colombo', value: 'Asia/Colombo' },
// Asia - Central Asia
{ key: 'Asia/Almaty', value: 'Asia/Almaty' },
{ key: 'Asia/Tashkent', value: 'Asia/Tashkent' },
{ key: 'Asia/Bishkek', value: 'Asia/Bishkek' },
{ key: 'Asia/Dushanbe', value: 'Asia/Dushanbe' },
// Asia - Western Asia/Middle East
{ key: 'Asia/Dubai', value: 'Asia/Dubai' },
{ key: 'Asia/Riyadh', value: 'Asia/Riyadh' },
{ key: 'Asia/Kuwait', value: 'Asia/Kuwait' },
{ key: 'Asia/Qatar', value: 'Asia/Qatar' },
{ key: 'Asia/Bahrain', value: 'Asia/Bahrain' },
{ key: 'Asia/Jerusalem', value: 'Asia/Jerusalem' },
{ key: 'Asia/Beirut', value: 'Asia/Beirut' },
{ key: 'Asia/Damascus', value: 'Asia/Damascus' },
{ key: 'Asia/Baghdad', value: 'Asia/Baghdad' },
{ key: 'Asia/Tehran', value: 'Asia/Tehran' },
// Asia - Russia
{ key: 'Europe/Moscow', value: 'Europe/Moscow' },
{ key: 'Asia/Yekaterinburg', value: 'Asia/Yekaterinburg' },
{ key: 'Asia/Novosibirsk', value: 'Asia/Novosibirsk' },
{ key: 'Asia/Krasnoyarsk', value: 'Asia/Krasnoyarsk' },
{ key: 'Asia/Irkutsk', value: 'Asia/Irkutsk' },
{ key: 'Asia/Yakutsk', value: 'Asia/Yakutsk' },
{ key: 'Asia/Vladivostok', value: 'Asia/Vladivostok' },
{ key: 'Asia/Sakhalin', value: 'Asia/Sakhalin' },
{ key: 'Asia/Kamchatka', value: 'Asia/Kamchatka' },
// Atlantic
{ key: 'Atlantic/Azores', value: 'Atlantic/Azores' },
{ key: 'Atlantic/Canary', value: 'Atlantic/Canary' },
{ key: 'Atlantic/Cape_Verde', value: 'Atlantic/Cape_Verde' },
{ key: 'Atlantic/Reykjavik', value: 'Atlantic/Reykjavik' },
// Australia & New Zealand
{ key: 'Australia/Sydney', value: 'Australia/Sydney' },
{ key: 'Australia/Melbourne', value: 'Australia/Melbourne' },
{ key: 'Australia/Brisbane', value: 'Australia/Brisbane' },
{ key: 'Australia/Perth', value: 'Australia/Perth' },
{ key: 'Australia/Adelaide', value: 'Australia/Adelaide' },
{ key: 'Australia/Darwin', value: 'Australia/Darwin' },
{ key: 'Australia/Hobart', value: 'Australia/Hobart' },
{ key: 'Pacific/Auckland', value: 'Pacific/Auckland' },
{ key: 'Pacific/Chatham', value: 'Pacific/Chatham' },
// Europe - Western Europe
{ key: 'Europe/London', value: 'Europe/London' },
{ key: 'Europe/Dublin', value: 'Europe/Dublin' },
{ key: 'Europe/Paris', value: 'Europe/Paris' },
{ key: 'Europe/Berlin', value: 'Europe/Berlin' },
{ key: 'Europe/Amsterdam', value: 'Europe/Amsterdam' },
{ key: 'Europe/Brussels', value: 'Europe/Brussels' },
{ key: 'Europe/Zurich', value: 'Europe/Zurich' },
{ key: 'Europe/Vienna', value: 'Europe/Vienna' },
{ key: 'Europe/Rome', value: 'Europe/Rome' },
{ key: 'Europe/Madrid', value: 'Europe/Madrid' },
{ key: 'Europe/Lisbon', value: 'Europe/Lisbon' },
// Europe - Northern Europe
{ key: 'Europe/Stockholm', value: 'Europe/Stockholm' },
{ key: 'Europe/Oslo', value: 'Europe/Oslo' },
{ key: 'Europe/Copenhagen', value: 'Europe/Copenhagen' },
{ key: 'Europe/Helsinki', value: 'Europe/Helsinki' },
// Europe - Eastern Europe
{ key: 'Europe/Warsaw', value: 'Europe/Warsaw' },
{ key: 'Europe/Prague', value: 'Europe/Prague' },
{ key: 'Europe/Budapest', value: 'Europe/Budapest' },
{ key: 'Europe/Bucharest', value: 'Europe/Bucharest' },
{ key: 'Europe/Sofia', value: 'Europe/Sofia' },
{ key: 'Europe/Athens', value: 'Europe/Athens' },
{ key: 'Europe/Istanbul', value: 'Europe/Istanbul' },
{ key: 'Europe/Kiev', value: 'Europe/Kiev' },
{ key: 'Europe/Minsk', value: 'Europe/Minsk' },
// Indian Ocean
{ key: 'Indian/Mauritius', value: 'Indian/Mauritius' },
{ key: 'Indian/Maldives', value: 'Indian/Maldives' },
// Pacific - Major Island Nations
{ key: 'Pacific/Honolulu', value: 'Pacific/Honolulu' },
{ key: 'Pacific/Fiji', value: 'Pacific/Fiji' },
{ key: 'Pacific/Guam', value: 'Pacific/Guam' },
{ key: 'Pacific/Tahiti', value: 'Pacific/Tahiti' },
{ key: 'Pacific/Apia', value: 'Pacific/Apia' },
{ key: 'Pacific/Tongatapu', value: 'Pacific/Tongatapu' },
{ key: 'Pacific/Port_Moresby', value: 'Pacific/Port_Moresby' },
{ key: 'Pacific/Noumea', value: 'Pacific/Noumea' },
];
export default timeZoneOptions;

View File

@@ -4,6 +4,7 @@ export default interface UiSettings {
shortDateFormat: string;
longDateFormat: string;
timeFormat: string;
timeZone: string;
firstDayOfWeek: number;
enableColorImpairedMode: boolean;
calendarWeekColumnHeader: string;

View File

@@ -46,6 +46,7 @@
"lodash": "4.17.21",
"mobile-detect": "1.4.5",
"moment": "2.30.1",
"moment-timezone": "0.6.0",
"mousetrap": "1.6.5",
"normalize.css": "8.0.1",
"prop-types": "15.8.1",
@@ -94,6 +95,7 @@
"@babel/preset-react": "7.27.1",
"@babel/preset-typescript": "7.27.1",
"@types/lodash": "4.14.195",
"@types/moment-timezone": "0.5.30",
"@types/mousetrap": "1.6.15",
"@types/qs": "6.9.16",
"@types/react-autosuggest": "10.1.11",

View File

@@ -343,6 +343,13 @@ namespace NzbDrone.Core.Configuration
set { SetValue("TimeFormat", value); }
}
public string TimeZone
{
get { return GetValue("TimeZone", ""); }
set { SetValue("TimeZone", value); }
}
public bool ShowRelativeDates
{
get { return GetValueBoolean("ShowRelativeDates", true); }

View File

@@ -68,6 +68,7 @@ namespace NzbDrone.Core.Configuration
string ShortDateFormat { get; set; }
string LongDateFormat { get; set; }
string TimeFormat { get; set; }
string TimeZone { get; set; }
bool ShowRelativeDates { get; set; }
bool EnableColorImpairedMode { get; set; }
int UILanguage { get; set; }

View File

@@ -13,6 +13,7 @@ namespace Sonarr.Api.V3.Config
public string ShortDateFormat { get; set; }
public string LongDateFormat { get; set; }
public string TimeFormat { get; set; }
public string TimeZone { get; set; }
public bool ShowRelativeDates { get; set; }
public bool EnableColorImpairedMode { get; set; }
@@ -32,6 +33,7 @@ namespace Sonarr.Api.V3.Config
ShortDateFormat = model.ShortDateFormat,
LongDateFormat = model.LongDateFormat,
TimeFormat = model.TimeFormat,
TimeZone = model.TimeZone,
ShowRelativeDates = model.ShowRelativeDates,
EnableColorImpairedMode = model.EnableColorImpairedMode,

View File

@@ -1395,6 +1395,13 @@
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e"
integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==
"@types/moment-timezone@0.5.30":
version "0.5.30"
resolved "https://registry.yarnpkg.com/@types/moment-timezone/-/moment-timezone-0.5.30.tgz#340ed45fe3e715f4a011f5cfceb7cb52aad46fc7"
integrity sha512-aDVfCsjYnAQaV/E9Qc24C5Njx1CoDjXsEgkxtp9NyXDpYu4CCbmclb6QhWloS9UTU/8YROUEEdEkWI0D7DxnKg==
dependencies:
moment-timezone "*"
"@types/mousetrap@1.6.15":
version "1.6.15"
resolved "https://registry.yarnpkg.com/@types/mousetrap/-/mousetrap-1.6.15.tgz#f144a0c539a4cef553a631824651d48267e53c86"
@@ -4751,7 +4758,14 @@ mobile-detect@1.4.5:
resolved "https://registry.yarnpkg.com/mobile-detect/-/mobile-detect-1.4.5.tgz#da393c3c413ca1a9bcdd9ced653c38281c0fb6ad"
integrity sha512-yc0LhH6tItlvfLBugVUEtgawwFU2sIe+cSdmRJJCTMZ5GEJyLxNyC/NIOAOGk67Fa8GNpOttO3Xz/1bHpXFD/g==
moment@2.30.1:
moment-timezone@*, moment-timezone@0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.6.0.tgz#c5a6519171f31a64739ea75d33f5c136c08ff608"
integrity sha512-ldA5lRNm3iJCWZcBCab4pnNL3HSZYXVb/3TYr75/1WCTWYuTqYUb5f/S384pncYjJ88lbO8Z4uPDvmoluHJc8Q==
dependencies:
moment "^2.29.4"
moment@2.30.1, moment@^2.29.4:
version "2.30.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==