diff --git a/OnlyT.AnalogueClock/ClockControl.cs b/OnlyT.AnalogueClock/ClockControl.cs index 208061ce0..35464913d 100644 --- a/OnlyT.AnalogueClock/ClockControl.cs +++ b/OnlyT.AnalogueClock/ClockControl.cs @@ -12,7 +12,7 @@ using System.Windows.Shapes; using System.Windows.Threading; - public class ClockControl : Control + public sealed class ClockControl : Control { public static readonly DependencyProperty IsFlatProperty = DependencyProperty.Register( @@ -67,7 +67,7 @@ public class ClockControl : Control typeof(DurationSector), typeof(ClockControl), new FrameworkPropertyMetadata(DurationSectorPropertyChanged)); - + private static readonly double ClockRadius = 250; private static readonly double SectorRadius = 230; private static readonly Point ClockOrigin = new Point(ClockRadius, ClockRadius); @@ -126,6 +126,8 @@ public ClockControl() } } + public event EventHandler QueryDateTimeEvent; + public bool DigitalTimeFormat24Hours { // ReSharper disable once PossibleNullReferenceException @@ -350,7 +352,7 @@ private void PositionClockHand(Line hand, double angle, double shadowOpacity) private void TimerCallback(object sender, EventArgs eventArgs) { - var now = DateTime.Now; + var now = GetNow(); PositionClockHand(_secondHand, CalculateAngleSeconds(now), _secondHandDropShadowOpacity); PositionClockHand(_minuteHand, CalculateAngleMinutes(now), _minuteHandDropShadowOpacity); @@ -414,7 +416,7 @@ private void StartupAnimation() private AnglesOfHands GenerateTargetAngles() { - DateTime targetTime = DateTime.Now; + var targetTime = GetNow(); return new AnglesOfHands { @@ -706,5 +708,22 @@ private void SetShowElapsedSector(bool value) } } } + + private DateTime GetNow() + { + var args = new DateTimeQueryEventArgs(); + OnQueryDateTime(args); + return args.DateTime; + } + + private void OnQueryDateTime(DateTimeQueryEventArgs e) + { + if (QueryDateTimeEvent == null) + { + e.DateTime = DateTime.Now; + } + + QueryDateTimeEvent?.Invoke(this, e); + } } } diff --git a/OnlyT.AnalogueClock/DateTimeQueryEventArgs.cs b/OnlyT.AnalogueClock/DateTimeQueryEventArgs.cs new file mode 100644 index 000000000..df09860a3 --- /dev/null +++ b/OnlyT.AnalogueClock/DateTimeQueryEventArgs.cs @@ -0,0 +1,9 @@ +namespace OnlyT.AnalogueClock +{ + using System; + + public class DateTimeQueryEventArgs : EventArgs + { + public DateTime DateTime { get; set; } + } +} diff --git a/OnlyT.AnalogueClock/OnlyT.AnalogueClock.csproj b/OnlyT.AnalogueClock/OnlyT.AnalogueClock.csproj index bd94f9400..df4aa66bb 100644 --- a/OnlyT.AnalogueClock/OnlyT.AnalogueClock.csproj +++ b/OnlyT.AnalogueClock/OnlyT.AnalogueClock.csproj @@ -64,6 +64,7 @@ + Code diff --git a/OnlyT.Common/Services/DateTime/DateTimeService.cs b/OnlyT.Common/Services/DateTime/DateTimeService.cs index 20c1b97f3..dc7345196 100644 --- a/OnlyT.Common/Services/DateTime/DateTimeService.cs +++ b/OnlyT.Common/Services/DateTime/DateTimeService.cs @@ -12,5 +12,10 @@ public System.DateTime UtcNow() { return System.DateTime.UtcNow; } + + public System.DateTime Today() + { + return System.DateTime.Today; + } } } diff --git a/OnlyT.Common/Services/DateTime/IDateTimeService.cs b/OnlyT.Common/Services/DateTime/IDateTimeService.cs index 2769a27b3..a32f68e8c 100644 --- a/OnlyT.Common/Services/DateTime/IDateTimeService.cs +++ b/OnlyT.Common/Services/DateTime/IDateTimeService.cs @@ -5,5 +5,7 @@ public interface IDateTimeService System.DateTime Now(); System.DateTime UtcNow(); + + System.DateTime Today(); } } diff --git a/OnlyT.CountdownTimer/CountdownControl.cs b/OnlyT.CountdownTimer/CountdownControl.cs index f7ef056de..b567fe695 100644 --- a/OnlyT.CountdownTimer/CountdownControl.cs +++ b/OnlyT.CountdownTimer/CountdownControl.cs @@ -61,6 +61,8 @@ public CountdownControl() _timer.Tick += TimerFire; } + public event EventHandler QueryUtcDateTimeEvent; + public event EventHandler TimeUpEvent; public int CountdownDurationMins @@ -87,7 +89,7 @@ public override void OnApplyTemplate() public void Start(int secsElapsed) { - _start = DateTime.UtcNow.AddSeconds(-secsElapsed); + _start = GetNowUtc().AddSeconds(-secsElapsed); _timer.Start(); } @@ -159,7 +161,7 @@ private void TimerFire(object sender, EventArgs e) if (_start != default(DateTime)) { var secsInCountdown = _countdownDurationMins * 60; - var secondsElapsed = (DateTime.UtcNow - _start).TotalSeconds; + var secondsElapsed = (GetNowUtc() - _start).TotalSeconds; var secondsLeft = secsInCountdown - secondsElapsed; if (secondsLeft >= 0) @@ -411,7 +413,7 @@ private string GetTimeText(int? secsLeft = null) else { var secsInCountdown = _countdownDurationMins * 60; - var secondsElapsed = (DateTime.UtcNow - _start).TotalSeconds; + var secondsElapsed = (GetNowUtc() - _start).TotalSeconds; secondsLeft = secsInCountdown - secondsElapsed + 1; } } @@ -437,5 +439,22 @@ private Size GetTextSize(string text, bool useExtent) return new Size(formattedText.Width, useExtent ? formattedText.Extent : formattedText.Height); } + + private DateTime GetNowUtc() + { + var args = new UtcDateTimeQueryEventArgs(); + OnQueryDateTime(args); + return args.UtcDateTime; + } + + private void OnQueryDateTime(UtcDateTimeQueryEventArgs e) + { + if (QueryUtcDateTimeEvent == null) + { + e.UtcDateTime = DateTime.UtcNow; + } + + QueryUtcDateTimeEvent?.Invoke(this, e); + } } } diff --git a/OnlyT.CountdownTimer/OnlyT.CountdownTimer.csproj b/OnlyT.CountdownTimer/OnlyT.CountdownTimer.csproj index f96339b37..0eafb990d 100644 --- a/OnlyT.CountdownTimer/OnlyT.CountdownTimer.csproj +++ b/OnlyT.CountdownTimer/OnlyT.CountdownTimer.csproj @@ -51,6 +51,7 @@ + diff --git a/OnlyT.CountdownTimer/UtcDateTimeQueryEventArgs.cs b/OnlyT.CountdownTimer/UtcDateTimeQueryEventArgs.cs new file mode 100644 index 000000000..a63de14d7 --- /dev/null +++ b/OnlyT.CountdownTimer/UtcDateTimeQueryEventArgs.cs @@ -0,0 +1,9 @@ +namespace OnlyT.CountdownTimer +{ + using System; + + public class UtcDateTimeQueryEventArgs : EventArgs + { + public DateTime UtcDateTime { get; set; } + } +} diff --git a/OnlyT.Tests/Mocks/MockDateTimeService.cs b/OnlyT.Tests/Mocks/MockDateTimeService.cs index 41c9567c5..21bb32d58 100644 --- a/OnlyT.Tests/Mocks/MockDateTimeService.cs +++ b/OnlyT.Tests/Mocks/MockDateTimeService.cs @@ -31,5 +31,10 @@ public DateTime UtcNow() { return Now(); } + + public DateTime Today() + { + return Now().Date; + } } } diff --git a/OnlyT.Tests/TestTimingReport.cs b/OnlyT.Tests/TestTimingReport.cs index 46b62348c..a9ec83604 100644 --- a/OnlyT.Tests/TestTimingReport.cs +++ b/OnlyT.Tests/TestTimingReport.cs @@ -145,7 +145,11 @@ private MeetingTimes StoreMidweekData( if (week == weekCount - 1) { - var file = TimingReportGeneration.ExecuteAsync(service, null).Result; + var file = TimingReportGeneration.ExecuteAsync( + service, + dateTimeService, + null).Result; + Assert.IsNotNull(file); } @@ -256,6 +260,11 @@ public DateTime UtcNow() { return Now(); } + + public DateTime Today() + { + return Now().Date; + } } } } diff --git a/OnlyT.Tests/TestViewModels.cs b/OnlyT.Tests/TestViewModels.cs index b972489b5..e01e9d2bd 100644 --- a/OnlyT.Tests/TestViewModels.cs +++ b/OnlyT.Tests/TestViewModels.cs @@ -3,6 +3,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Mocks; using Moq; + using OnlyT.Common.Services.DateTime; using OnlyT.Services.CommandLine; using OnlyT.Services.Report; using OnlyT.Services.Snackbar; @@ -32,6 +33,7 @@ public void TestOperatorViewStartStop() Mock commandLineService = new Mock(); Mock timingDataService = new Mock(); Mock snackbarService = new Mock(); + IDateTimeService dateTimeService = new MockDateTimeService(); var vm = new OperatorPageViewModel( timerService.Object, @@ -41,7 +43,8 @@ public void TestOperatorViewStartStop() commandLineService.Object, bellService.Object, timingDataService.Object, - snackbarService.Object); + snackbarService.Object, + dateTimeService); Assert.IsFalse(vm.IsRunning); Assert.IsFalse(vm.IsManualMode); diff --git a/OnlyT.sln.DotSettings b/OnlyT.sln.DotSettings index a0feae0d3..1dcf290e9 100644 --- a/OnlyT.sln.DotSettings +++ b/OnlyT.sln.DotSettings @@ -7,6 +7,7 @@ True True True + True True True True diff --git a/OnlyT/MeetingTalkTimesFeed/TimesFeed.cs b/OnlyT/MeetingTalkTimesFeed/TimesFeed.cs index b7f220bd3..0ec9c958c 100644 --- a/OnlyT/MeetingTalkTimesFeed/TimesFeed.cs +++ b/OnlyT/MeetingTalkTimesFeed/TimesFeed.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using Newtonsoft.Json; + using OnlyT.Common.Services.DateTime; using Serilog; using Utils; @@ -21,9 +22,9 @@ public TimesFeed() _localFeedFile = Path.Combine(FileUtils.GetAppDataFolder(), "feed.json"); } - public Meeting GetMeetingDataForToday() + public Meeting GetMeetingDataForToday(IDateTimeService dateTimeService) { - LoadFile(); + LoadFile(dateTimeService.Now().Date); return GetMeetingDataForTodayInternal(_meetingData); } @@ -70,14 +71,14 @@ public Meeting GetSampleMidweekMeetingDataForTesting(DateTime theDate) return result; } - private bool LocalFileTooOld() + private bool LocalFileTooOld(DateTime today) { - bool tooOld = true; + var tooOld = true; if (File.Exists(_localFeedFile)) { var created = File.GetLastWriteTime(_localFeedFile); - var diff = DateTime.Now - created; + var diff = today - created; if (diff.TotalDays <= _tooOldDays) { tooOld = false; @@ -87,19 +88,19 @@ private bool LocalFileTooOld() return tooOld; } - private void LoadFile() + private void LoadFile(DateTime today) { if (_meetingData == null) { - _meetingData = LoadFileInternal(); + _meetingData = LoadFileInternal(today); } } - private IReadOnlyCollection LoadFileInternal() + private IReadOnlyCollection LoadFileInternal(DateTime today) { List result = null; - var needRefresh = LocalFileTooOld(); + var needRefresh = LocalFileTooOld(today); if (!needRefresh) { diff --git a/OnlyT/Properties/Resources.Designer.cs b/OnlyT/Properties/Resources.Designer.cs index 5adf00d4d..0759a4e83 100644 --- a/OnlyT/Properties/Resources.Designer.cs +++ b/OnlyT/Properties/Resources.Designer.cs @@ -1442,6 +1442,15 @@ public static string WEEKEND { } } + /// + /// Looks up a localized string similar to Weekend includes Friday. + /// + public static string WEEKEND_INCLUDES_FRIDAY { + get { + return ResourceManager.GetString("WEEKEND_INCLUDES_FRIDAY", resourceCulture); + } + } + /// /// Looks up a localized string similar to {0} seconds. /// diff --git a/OnlyT/Properties/Resources.resx b/OnlyT/Properties/Resources.resx index 0d8251c9e..88ad2c681 100644 --- a/OnlyT/Properties/Resources.resx +++ b/OnlyT/Properties/Resources.resx @@ -673,4 +673,8 @@ Flat clock See Settings => Timer window. Refres to whether the analogue clock is flat or 3D + + Weekend includes Friday + See Settings => Automatic mode + \ No newline at end of file diff --git a/OnlyT/Services/Automate/AutomateService.cs b/OnlyT/Services/Automate/AutomateService.cs index 5a83c1e6d..daa8167c7 100644 --- a/OnlyT/Services/Automate/AutomateService.cs +++ b/OnlyT/Services/Automate/AutomateService.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; using System.Windows.Threading; + using OnlyT.Common.Services.DateTime; using OnlyT.Models; using OnlyT.Services.Options; using OnlyT.Services.TalkSchedule; @@ -15,6 +16,7 @@ internal class AutomateService : IAutomateService private readonly IOptionsService _optionsService; private readonly ITalkTimerService _timerService; private readonly ITalkScheduleService _scheduleService; + private readonly IDateTimeService _dateTimeService; private readonly DispatcherTimer _timer = new DispatcherTimer(); private readonly Stopwatch _stopwatch = new Stopwatch(); private readonly Random _random = new Random(); @@ -25,11 +27,13 @@ internal class AutomateService : IAutomateService public AutomateService( IOptionsService optionsService, ITalkTimerService timerService, - ITalkScheduleService scheduleService) + ITalkScheduleService scheduleService, + IDateTimeService dateTimeService) { _optionsService = optionsService; _timerService = timerService; _scheduleService = scheduleService; + _dateTimeService = dateTimeService; } public void Execute() @@ -46,7 +50,7 @@ public void Execute() private void TimerTick(object sender, System.EventArgs e) { - var now = DateTime.Now; + var now = _dateTimeService.Now(); if (!_stopwatch.IsRunning) { diff --git a/OnlyT/Services/CountdownTimer/CountdownTimerTriggerService.cs b/OnlyT/Services/CountdownTimer/CountdownTimerTriggerService.cs index 49399f3e1..959651040 100644 --- a/OnlyT/Services/CountdownTimer/CountdownTimerTriggerService.cs +++ b/OnlyT/Services/CountdownTimer/CountdownTimerTriggerService.cs @@ -3,19 +3,25 @@ using System; using System.Collections.Generic; using System.Linq; + using OnlyT.Common.Services.DateTime; using Options; using Options.MeetingStartTimes; - + // ReSharper disable once ClassNeverInstantiated.Global internal sealed class CountdownTimerTriggerService : ICountdownTimerTriggerService { private readonly object _locker = new object(); private readonly IOptionsService _optionsService; + private readonly IDateTimeService _dateTimeService; private List _triggerPeriods; - public CountdownTimerTriggerService(IOptionsService optionsService) + public CountdownTimerTriggerService( + IOptionsService optionsService, + IDateTimeService dateTimeService) { _optionsService = optionsService; + _dateTimeService = dateTimeService; + UpdateTriggerPeriods(); } @@ -39,7 +45,7 @@ public bool IsInCountdownPeriod(out int secondsOffset) { if (_triggerPeriods != null) { - var now = DateTime.Now; + var now = _dateTimeService.Now(); var trigger = _triggerPeriods.FirstOrDefault(x => x.Start <= now && x.End > now); if (trigger != null) @@ -58,7 +64,7 @@ private void CalculateTriggerPeriods(IEnumerable meetingStartT { var triggerPeriods = new List(); - DateTime today = DateTime.Today; // local time + DateTime today = _dateTimeService.Today(); // local time int countdownDurationMins = _optionsService.Options.CountdownDurationMins; foreach (var time in meetingStartTimes) diff --git a/OnlyT/Services/Options/Options.cs b/OnlyT/Services/Options/Options.cs index 471d75519..d2b4cabf5 100644 --- a/OnlyT/Services/Options/Options.cs +++ b/OnlyT/Services/Options/Options.cs @@ -144,6 +144,8 @@ public Options() public bool ClockIsFlat { get; set; } + public bool WeekendIncludesFriday { get; set; } + public CountdownDurationItem[] GetCountdownDurationItems() { var result = new List(); diff --git a/OnlyT/Services/Options/OptionsService.cs b/OnlyT/Services/Options/OptionsService.cs index 8550568ea..ef0bdbe80 100644 --- a/OnlyT/Services/Options/OptionsService.cs +++ b/OnlyT/Services/Options/OptionsService.cs @@ -10,6 +10,7 @@ using CommandLine; using GalaSoft.MvvmLight.Messaging; using Newtonsoft.Json; + using OnlyT.Common.Services.DateTime; using OnlyT.Services.LogLevelSwitch; using OnlyT.Services.Monitors; using OnlyT.ViewModel.Messages; @@ -25,6 +26,7 @@ internal class OptionsService : IOptionsService private readonly ICommandLineService _commandLineService; private readonly ILogLevelSwitchService _logLevelSwitchService; private readonly IMonitorsService _monitorsService; + private readonly IDateTimeService _dateTimeService; private readonly int _optionsVersion = 1; private Options _options; private string _optionsFilePath; @@ -33,11 +35,13 @@ internal class OptionsService : IOptionsService public OptionsService( ICommandLineService commandLineService, ILogLevelSwitchService logLevelSwitchService, - IMonitorsService monitorsService) + IMonitorsService monitorsService, + IDateTimeService dateTimeService) { _commandLineService = commandLineService; _logLevelSwitchService = logLevelSwitchService; _monitorsService = monitorsService; + _dateTimeService = dateTimeService; Messenger.Default.Register(this, OnLogLevelChanged); } @@ -258,7 +262,7 @@ private void ResetCircuitVisit() private bool IsWeekend() { - var now = DateTime.Now; + var now = _dateTimeService.Now(); return now.DayOfWeek == DayOfWeek.Saturday || now.DayOfWeek == DayOfWeek.Sunday; } diff --git a/OnlyT/Services/Report/TimingReportGeneration.cs b/OnlyT/Services/Report/TimingReportGeneration.cs index 1d177bb00..86319eeda 100644 --- a/OnlyT/Services/Report/TimingReportGeneration.cs +++ b/OnlyT/Services/Report/TimingReportGeneration.cs @@ -3,6 +3,7 @@ using System; using System.IO; using System.Threading.Tasks; + using OnlyT.Common.Services.DateTime; using OnlyT.Report.Pdf; using OnlyT.Utils; using Serilog; @@ -12,6 +13,7 @@ internal static class TimingReportGeneration // generate report and return file path (or null) public static Task ExecuteAsync( ILocalTimingDataStoreService dataService, + IDateTimeService dateTimeService, string commandLineIdentifier) { return Task.Run(() => @@ -31,7 +33,7 @@ public static Task ExecuteAsync( return null; } - var yearFolder = GetDatedOutputFolder(outputFolder); + var yearFolder = GetDatedOutputFolder(outputFolder, dateTimeService.Now()); Directory.CreateDirectory(yearFolder); if (Directory.Exists(yearFolder)) @@ -50,9 +52,8 @@ public static Task ExecuteAsync( }); } - private static string GetDatedOutputFolder(string outputFolder) + private static string GetDatedOutputFolder(string outputFolder, DateTime now) { - var now = DateTime.Now; var monthFolderName = $"{now.Year}-{now.Month:D2}"; return Path.Combine(outputFolder, now.Year.ToString(), monthFolderName); } diff --git a/OnlyT/Services/TalkSchedule/TalkScheduleAuto.cs b/OnlyT/Services/TalkSchedule/TalkScheduleAuto.cs index 241090fd3..378db8713 100644 --- a/OnlyT/Services/TalkSchedule/TalkScheduleAuto.cs +++ b/OnlyT/Services/TalkSchedule/TalkScheduleAuto.cs @@ -15,7 +15,6 @@ //// 8:07:00 (67:00) Cong study(30 mins) //// 8:37:00 (97:00) Concluding comments(3 mins) //// 8:40:00 Song / prayer - //// 7:00:00 (00:00) Start, song / prayer (19:00 - 19:05) //// 7:05:00 (05:00) Opening comments (19:05 - 19:08) //// 7:08:20 (08:20) Talk (10 mins) (19:08:20 - 19:18:20) @@ -38,6 +37,7 @@ using System.Linq; using MeetingTalkTimesFeed; using Models; + using OnlyT.Common.Services.DateTime; using Options; /// @@ -59,10 +59,12 @@ internal static class TalkScheduleAuto /// Gets the talk schedule. /// /// Options service. + /// DateTime service. /// True if current date is greater than 5 Jan 2020. /// A collection of TalkScheduleItem. public static IEnumerable Read( IOptionsService optionsService, + IDateTimeService dateTimeService, bool isJanuary2020OrLater) { var isCircuitVisit = optionsService.Options.IsCircuitVisit; @@ -73,7 +75,7 @@ public static IEnumerable Read( isCircuitVisit, optionsService.Options.IsBellEnabled && optionsService.Options.AutoBell, isJanuary2020OrLater, - GetFeedForToday()); + GetFeedForToday(dateTimeService)); } public static IEnumerable GetMidweekScheduleForTesting( @@ -87,9 +89,9 @@ public static IEnumerable GetMidweekScheduleForTesting( new TimesFeed().GetSampleMidweekMeetingDataForTesting(theDate)); } - private static Meeting GetFeedForToday() + private static Meeting GetFeedForToday(IDateTimeService dateTimeService) { - var feed = new TimesFeed().GetMeetingDataForToday(); + var feed = new TimesFeed().GetMeetingDataForToday(dateTimeService); if (feed != null) { SuccessGettingAutoFeedForMidWeekMtg = true; diff --git a/OnlyT/Services/TalkSchedule/TalkScheduleService.cs b/OnlyT/Services/TalkSchedule/TalkScheduleService.cs index ec82a0b22..46ef98617 100644 --- a/OnlyT/Services/TalkSchedule/TalkScheduleService.cs +++ b/OnlyT/Services/TalkSchedule/TalkScheduleService.cs @@ -18,6 +18,7 @@ internal sealed class TalkScheduleService : ITalkScheduleService private static readonly DateTime _january2020Change = new DateTime(2020, 1, 6); private readonly IOptionsService _optionsService; + private readonly IDateTimeService _dateTimeService; private readonly bool _isJanuary2020OrLater; // the "talk_schedule.xml" file may exist in MyDocs\OnlyT.. @@ -30,6 +31,7 @@ public TalkScheduleService( IDateTimeService dateTimeService) { _optionsService = optionsService; + _dateTimeService = dateTimeService; _isJanuary2020OrLater = dateTimeService.Now().Date >= _january2020Change; @@ -42,7 +44,7 @@ public TalkScheduleService( public void Reset() { _fileBasedSchedule = new Lazy>(() => TalkScheduleFileBased.Read(_optionsService.Options.AutoBell)); - _autoSchedule = new Lazy>(() => TalkScheduleAuto.Read(_optionsService, _isJanuary2020OrLater)); + _autoSchedule = new Lazy>(() => TalkScheduleAuto.Read(_optionsService, _dateTimeService, _isJanuary2020OrLater)); _manualSchedule = new Lazy>(() => TalkScheduleManual.Read(_optionsService)); } diff --git a/OnlyT/ViewModel/MainViewModel.cs b/OnlyT/ViewModel/MainViewModel.cs index 05bd4f036..3fdcbb400 100644 --- a/OnlyT/ViewModel/MainViewModel.cs +++ b/OnlyT/ViewModel/MainViewModel.cs @@ -18,6 +18,7 @@ namespace OnlyT.ViewModel using MaterialDesignThemes.Wpf; using Messages; using Models; + using OnlyT.Common.Services.DateTime; using OnlyT.Services.JwLibrary; using OnlyT.Services.Snackbar; using Serilog; @@ -44,6 +45,7 @@ public class MainViewModel : ViewModelBase private readonly ICountdownTimerTriggerService _countdownTimerTriggerService; private readonly ITalkTimerService _timerService; private readonly ICommandLineService _commandLineService; + private readonly IDateTimeService _dateTimeService; private readonly IHttpServer _httpServer; private readonly (int dpiX, int dpiY) _systemDpi; private readonly ISnackbarService _snackbarService; @@ -61,9 +63,11 @@ public MainViewModel( ISnackbarService snackbarService, IHttpServer httpServer, ICommandLineService commandLineService, - ICountdownTimerTriggerService countdownTimerTriggerService) + ICountdownTimerTriggerService countdownTimerTriggerService, + IDateTimeService dateTimeService) { _commandLineService = commandLineService; + _dateTimeService = dateTimeService; if (commandLineService.NoGpu || ForceSoftwareRendering()) { @@ -305,9 +309,9 @@ private void HeartbeatTimerTick(object sender, EventArgs e) private void ManageScheduleOnHeartbeat() { - if ((DateTime.Now - _lastRefreshedSchedule).Seconds > 10) + if ((_dateTimeService.Now() - _lastRefreshedSchedule).Seconds > 10) { - _lastRefreshedSchedule = DateTime.Now; + _lastRefreshedSchedule = _dateTimeService.Now(); Messenger.Default.Send(new RefreshScheduleMessage()); } } @@ -411,7 +415,7 @@ private bool OpenCountdownWindow(int offsetSeconds) var targetMonitor = _monitorsService.GetMonitorItem(_optionsService.Options.CountdownMonitorId); if (targetMonitor != null) { - _countdownWindow = new CountdownWindow(); + _countdownWindow = new CountdownWindow(_dateTimeService); _countdownWindow.TimeUpEvent += OnCountdownTimeUp; ShowWindowFullScreenOnTop(_countdownWindow, targetMonitor); _countdownWindow.Start(offsetSeconds); @@ -442,7 +446,7 @@ private void OpenTimerWindow() var targetMonitor = _monitorsService.GetMonitorItem(_optionsService.Options.TimerMonitorId); if (targetMonitor != null) { - _timerWindow = new TimerOutputWindow(_optionsService); + _timerWindow = new TimerOutputWindow(_optionsService, _dateTimeService); ShowWindowFullScreenOnTop(_timerWindow, targetMonitor); } } diff --git a/OnlyT/ViewModel/OperatorPageViewModel.cs b/OnlyT/ViewModel/OperatorPageViewModel.cs index e7b4f1e83..cbaa2ff61 100644 --- a/OnlyT/ViewModel/OperatorPageViewModel.cs +++ b/OnlyT/ViewModel/OperatorPageViewModel.cs @@ -15,7 +15,7 @@ using GalaSoft.MvvmLight.Messaging; using Messages; using Models; - using OnlyT.Services.Automate; + using OnlyT.Common.Services.DateTime; using OnlyT.Services.Report; using OnlyT.Services.Snackbar; using Serilog; @@ -52,6 +52,7 @@ public class OperatorPageViewModel : ViewModelBase, IPage private readonly IBellService _bellService; private readonly ICommandLineService _commandLineService; private readonly ILocalTimingDataStoreService _timingDataService; + private readonly IDateTimeService _dateTimeService; private readonly ISnackbarService _snackbarService; private int _secondsElapsed; @@ -76,7 +77,8 @@ public OperatorPageViewModel( ICommandLineService commandLineService, IBellService bellService, ILocalTimingDataStoreService timingDataService, - ISnackbarService snackbarService) + ISnackbarService snackbarService, + IDateTimeService dateTimeService) { _scheduleService = scheduleService; _optionsService = optionsService; @@ -86,6 +88,8 @@ public OperatorPageViewModel( _timerService = timerService; _snackbarService = snackbarService; _timingDataService = timingDataService; + _dateTimeService = dateTimeService; + _timerService.TimerChangedEvent += TimerChangedHandler; _countUp = _optionsService.Options.CountUp; @@ -452,7 +456,7 @@ private void StartTimer() Task.Run(() => { - int ms = DateTime.Now.Millisecond; + int ms = _dateTimeService.Now().Millisecond; if (ms > 100) { // sync to the second (so that the timer window clock and countdown @@ -539,7 +543,7 @@ private void StoreTimerDataForStartOfMeeting() private DateTime CalculateStartOfMeeting() { - var estimated = DateUtils.GetNearestQuarterOfAnHour(DateTime.Now); + var estimated = DateUtils.GetNearestQuarterOfAnHour(_dateTimeService.Now()); if (_meetingStartTimeFromCountdown == null) { return estimated; @@ -572,7 +576,7 @@ private void StoreEndOfMeetingData() { Log.Logger.Debug("Storing end of meeting timer data"); - var songStart = DateTime.Now.AddSeconds(5); + var songStart = _dateTimeService.Now().AddSeconds(5); // a guess! var actualMeetingEnd = songStart.AddMinutes(5); @@ -724,7 +728,7 @@ private void OnCountdownWindowStatusChanged(CountdownWindowStatusChangedMessage } else { - _meetingStartTimeFromCountdown = DateUtils.GetNearestMinute(DateTime.Now); + _meetingStartTimeFromCountdown = DateUtils.GetNearestMinute(_dateTimeService.Now()); } IsCountdownActive = message.Showing; @@ -1053,6 +1057,7 @@ private async Task GenerateTimingReportAsync() var reportPath = await TimingReportGeneration.ExecuteAsync( _timingDataService, + _dateTimeService, _commandLineService.OptionsIdentifier).ConfigureAwait(false); if (string.IsNullOrEmpty(reportPath)) diff --git a/OnlyT/ViewModel/SettingsPageViewModel.cs b/OnlyT/ViewModel/SettingsPageViewModel.cs index 47c5094a4..8ae2c3d42 100644 --- a/OnlyT/ViewModel/SettingsPageViewModel.cs +++ b/OnlyT/ViewModel/SettingsPageViewModel.cs @@ -13,8 +13,8 @@ using GalaSoft.MvvmLight.Messaging; using Messages; using Models; + using OnlyT.Common.Services.DateTime; using OnlyT.CountdownTimer; - using OnlyT.Services.CommandLine; using OnlyT.Services.Snackbar; using Serilog; using Serilog.Events; @@ -39,6 +39,7 @@ public class SettingsPageViewModel : ViewModelBase, IPage private readonly IMonitorsService _monitorsService; private readonly IBellService _bellService; private readonly ICountdownTimerTriggerService _countdownTimerService; + private readonly IDateTimeService _dateTimeService; private readonly ClockHourFormatItem[] _clockHourFormats; private readonly AdaptiveModeItem[] _adaptiveModes; private readonly FullScreenClockModeItem[] _timeOfDayModes; @@ -52,7 +53,7 @@ public SettingsPageViewModel( IOptionsService optionsService, ISnackbarService snackbarService, ICountdownTimerTriggerService countdownTimerService, - ICommandLineService commandLineService) + IDateTimeService dateTimeService) { // subscriptions... Messenger.Default.Register(this, OnShutDown); @@ -63,7 +64,8 @@ public SettingsPageViewModel( _monitorsService = monitorsService; _bellService = bellService; _countdownTimerService = countdownTimerService; - + _dateTimeService = dateTimeService; + _monitors = GetSystemMonitors(); _languages = GetSupportedLanguages(); _operatingModes = GetOperatingModes(); @@ -388,6 +390,24 @@ public bool IsCircuitVisit } } + public bool WeekendIncludesFriday + { + get => _optionsService.Options.WeekendIncludesFriday; + set + { + if (_optionsService.Options.WeekendIncludesFriday != value) + { + _optionsService.Options.WeekendIncludesFriday = value; + RaisePropertyChanged(); + + if (_dateTimeService.Now().DayOfWeek == DayOfWeek.Friday) + { + Messenger.Default.Send(new AutoMeetingChangedMessage()); + } + } + } + } + public bool ShowCircuitVisitToggle { get => _optionsService.Options.ShowCircuitVisitToggle; @@ -884,7 +904,7 @@ private IEnumerable GetPorts() private ClockHourFormatItem[] GetClockHourFormats() { - var cultureUsesAmPm = !string.IsNullOrEmpty(DateTime.Now.ToString("tt", CultureInfo.CurrentUICulture)); + var cultureUsesAmPm = !string.IsNullOrEmpty(_dateTimeService.Now().ToString("tt", CultureInfo.CurrentUICulture)); var result = new List { diff --git a/OnlyT/ViewModel/TimerOutputWindowViewModel.cs b/OnlyT/ViewModel/TimerOutputWindowViewModel.cs index f3a58da93..4d9abb05e 100644 --- a/OnlyT/ViewModel/TimerOutputWindowViewModel.cs +++ b/OnlyT/ViewModel/TimerOutputWindowViewModel.cs @@ -8,6 +8,7 @@ using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.Messaging; using Messages; + using OnlyT.Common.Services.DateTime; using Services.Options; using Utils; @@ -16,6 +17,7 @@ public class TimerOutputWindowViewModel : ViewModelBase { private static readonly int _secsPerHour = 60 * 60; private readonly IOptionsService _optionsService; + private readonly IDateTimeService _dateTimeService; private int _analogueClockColumnWidthPercentage = -1; private string _timeString; @@ -24,10 +26,13 @@ public class TimerOutputWindowViewModel : ViewModelBase private DurationSector _durationSector; private bool _showTimeOfDayUnderTimer; - public TimerOutputWindowViewModel(IOptionsService optionsService) + public TimerOutputWindowViewModel( + IOptionsService optionsService, + IDateTimeService dateTimeService) { _optionsService = optionsService; - + _dateTimeService = dateTimeService; + AnalogueClockColumnWidthPercentage = _optionsService.Options.AnalogueClockWidthPercent; ShowTimeOfDayUnderTimer = _optionsService.Options.ShowTimeOfDayUnderTimer; @@ -213,7 +218,7 @@ private void InitOverallDurationSector(int elapsedSecs, int remainingSecs) // can't display duration sector effectively when >= 1 hr if (remainingSecs < _secsPerHour) { - var now = DateTime.Now; + var now = _dateTimeService.Now(); var startAngle = CalcAngleFromTime(now); var endTime = now.AddSeconds(remainingSecs); @@ -252,7 +257,7 @@ private void OnTimerChanged(TimerChangedMessage message) if (DurationSector != null) { - var now = DateTime.Now; + var now = _dateTimeService.Now(); var currentAngle = CalcAngleFromTime(now); // prevent gratuitous updates diff --git a/OnlyT/WebServer/Controllers/ApiRouter.cs b/OnlyT/WebServer/Controllers/ApiRouter.cs index f6d83ec4c..dee7a765f 100644 --- a/OnlyT/WebServer/Controllers/ApiRouter.cs +++ b/OnlyT/WebServer/Controllers/ApiRouter.cs @@ -4,6 +4,7 @@ using System.Net; using ErrorHandling; using Models; + using OnlyT.Common.Services.DateTime; using Services.Bell; using Services.Options; using Services.TalkSchedule; @@ -17,7 +18,8 @@ internal class ApiRouter : BaseApiController private readonly ApiThrottler _apiThrottler; private readonly IOptionsService _optionsService; - + private readonly IDateTimeService _dateTimeService; + private readonly Lazy _timersApiController; private readonly Lazy _dateTimeApiController; private readonly Lazy _bellApiController; @@ -29,11 +31,13 @@ public ApiRouter( IOptionsService optionsService, IBellService bellService, ITalkTimerService timerService, - ITalkScheduleService talksService) + ITalkScheduleService talksService, + IDateTimeService dateTimeService) { _apiThrottler = apiThrottler; _optionsService = optionsService; - + _dateTimeService = dateTimeService; + _timersApiController = new Lazy(() => new TimersApiController(timerService, talksService, _optionsService, _apiThrottler)); @@ -100,7 +104,7 @@ public void HandleRequest(HttpListenerRequest request, HttpListenerResponse resp case "datetime": DisableCache(response); - _dateTimeApiController.Value.Handler(request, response); + _dateTimeApiController.Value.Handler(request, response, _dateTimeService.Now()); break; case "system": diff --git a/OnlyT/WebServer/Controllers/ClockWebPageController.cs b/OnlyT/WebServer/Controllers/ClockWebPageController.cs index daf072dba..47ece6891 100644 --- a/OnlyT/WebServer/Controllers/ClockWebPageController.cs +++ b/OnlyT/WebServer/Controllers/ClockWebPageController.cs @@ -10,12 +10,15 @@ internal class ClockWebPageController { private static readonly Lazy WebPageHtml = new Lazy(GenerateWebPageHtml); - public void HandleRequestForTimerData(HttpListenerResponse response, TimerInfoEventArgs timerInfo) + public void HandleRequestForTimerData( + HttpListenerResponse response, + TimerInfoEventArgs timerInfo, + DateTime now) { response.ContentType = "text/xml"; response.ContentEncoding = Encoding.UTF8; - string responseString = CreateXml(timerInfo); + string responseString = CreateXml(timerInfo, now); byte[] buffer = Encoding.UTF8.GetBytes(responseString); response.ContentLength64 = buffer.Length; @@ -44,7 +47,7 @@ private static byte[] GenerateWebPageHtml() return Encoding.UTF8.GetBytes(Uglify.Html(content).Code); } - private string CreateXml(TimerInfoEventArgs timerInfo) + private string CreateXml(TimerInfoEventArgs timerInfo, DateTime now) { var sb = new StringBuilder(); @@ -59,7 +62,6 @@ private string CreateXml(TimerInfoEventArgs timerInfo) case ClockServerMode.TimeOfDay: // in this mode mins and secs hold the total offset time into the day - DateTime now = DateTime.Now; int use24Hrs = timerInfo.Use24HrFormat ? 1 : 0; sb.AppendLine( $" "); diff --git a/OnlyT/WebServer/Controllers/DateTimeApiController.cs b/OnlyT/WebServer/Controllers/DateTimeApiController.cs index 13326d649..a83e222a8 100644 --- a/OnlyT/WebServer/Controllers/DateTimeApiController.cs +++ b/OnlyT/WebServer/Controllers/DateTimeApiController.cs @@ -14,7 +14,10 @@ public DateTimeApiController(ApiThrottler apiThrottler) _apiThrottler = apiThrottler; } - public void Handler(HttpListenerRequest request, HttpListenerResponse response) + public void Handler( + HttpListenerRequest request, + HttpListenerResponse response, + DateTime now) { CheckMethodGet(request); CheckSegmentLength(request, 4); @@ -23,16 +26,14 @@ public void Handler(HttpListenerRequest request, HttpListenerResponse response) // segments: "/" "api/" "v1/" "datetime/" // system clock - var dt = DateTime.Now; - var lt = new LocalTime { - Year = dt.Year, - Month = dt.Month, - Day = dt.Day, - Hour = dt.Hour, - Min = dt.Minute, - Second = dt.Second + Year = now.Year, + Month = now.Month, + Day = now.Day, + Hour = now.Hour, + Min = now.Minute, + Second = now.Second }; WriteResponse(response, lt); diff --git a/OnlyT/WebServer/HttpServer.cs b/OnlyT/WebServer/HttpServer.cs index 36f9174be..5da1671f7 100644 --- a/OnlyT/WebServer/HttpServer.cs +++ b/OnlyT/WebServer/HttpServer.cs @@ -1,6 +1,5 @@ namespace OnlyT.WebServer { - // ReSharper disable CatchAllClause using System; using System.Net; using System.Threading.Tasks; @@ -8,6 +7,7 @@ using ErrorHandling; using EventArgs; using Models; + using OnlyT.Common.Services.DateTime; using Serilog; using Services.Bell; using Services.Options; @@ -19,6 +19,7 @@ internal sealed class HttpServer : IHttpServer, IDisposable { private readonly IOptionsService _optionsService; + private readonly IDateTimeService _dateTimeService; private readonly ApiThrottler _apiThrottler; private readonly ApiRouter _apiRouter; private HttpListener _listener; @@ -28,13 +29,21 @@ public HttpServer( IOptionsService optionsService, IBellService bellService, ITalkTimerService timerService, - ITalkScheduleService talksService) + ITalkScheduleService talksService, + IDateTimeService dateTimeService) { _optionsService = optionsService; - + _dateTimeService = dateTimeService; + _apiThrottler = new ApiThrottler(optionsService); - _apiRouter = new ApiRouter(_apiThrottler, _optionsService, bellService, timerService, talksService); + _apiRouter = new ApiRouter( + _apiThrottler, + _optionsService, + bellService, + timerService, + talksService, + dateTimeService); } public event EventHandler RequestForTimerDataEvent; @@ -190,7 +199,7 @@ private void HandleRequestForClockWebPageTimerData( OnRequestForTimerDataEvent(timerInfo); ClockWebPageController controller = new ClockWebPageController(); - controller.HandleRequestForTimerData(response, timerInfo); + controller.HandleRequestForTimerData(response, timerInfo, _dateTimeService.Now()); } } diff --git a/OnlyT/Windows/CountdownWindow.xaml b/OnlyT/Windows/CountdownWindow.xaml index b4822f565..c988a4efa 100644 --- a/OnlyT/Windows/CountdownWindow.xaml +++ b/OnlyT/Windows/CountdownWindow.xaml @@ -33,10 +33,12 @@ - + diff --git a/OnlyT/Windows/CountdownWindow.xaml.cs b/OnlyT/Windows/CountdownWindow.xaml.cs index 9c0d24cfe..b915144fd 100644 --- a/OnlyT/Windows/CountdownWindow.xaml.cs +++ b/OnlyT/Windows/CountdownWindow.xaml.cs @@ -4,15 +4,21 @@ using System.Threading.Tasks; using System.Windows; using GalaSoft.MvvmLight.Threading; + using OnlyT.Common.Services.DateTime; + using OnlyT.CountdownTimer; /// /// Interaction logic for CountdownWindow.xaml /// public partial class CountdownWindow : Window { - public CountdownWindow() + private readonly IDateTimeService _dateTimeService; + + public CountdownWindow(IDateTimeService dateTimeService) { InitializeComponent(); + + _dateTimeService = dateTimeService; } public event EventHandler TimeUpEvent; @@ -41,5 +47,10 @@ private void OnTimeUpEvent() { TimeUpEvent?.Invoke(this, EventArgs.Empty); } + + private void CountDownQueryUtcDateTime(object sender, UtcDateTimeQueryEventArgs e) + { + e.UtcDateTime = _dateTimeService.UtcNow(); + } } } diff --git a/OnlyT/Windows/SettingsPage.xaml b/OnlyT/Windows/SettingsPage.xaml index 8f0017cde..14c235b41 100644 --- a/OnlyT/Windows/SettingsPage.xaml +++ b/OnlyT/Windows/SettingsPage.xaml @@ -155,6 +155,10 @@ materialDesign:ComboBoxAssist.ClassicMode="true" SelectedValue="{Binding WeekendAdaptiveMode, Mode=TwoWay}"/> + + diff --git a/OnlyT/Windows/TimerOutputWindow.xaml b/OnlyT/Windows/TimerOutputWindow.xaml index 97f44e7cc..26013bbfa 100644 --- a/OnlyT/Windows/TimerOutputWindow.xaml +++ b/OnlyT/Windows/TimerOutputWindow.xaml @@ -39,6 +39,7 @@ (this, OnTimerStarted); Messenger.Default.Register(this, OnTimerStopped); @@ -292,5 +298,10 @@ private void WindowClosing(object sender, System.ComponentModel.CancelEventArgs e.Cancel = true; } } + + private void ClockQueryDateTimeEvent(object sender, DateTimeQueryEventArgs e) + { + e.DateTime = _dateTimeService.Now(); + } } }