C#开发系统服务时用的定时器组件(修正版)

来源:互联网 发布:湖南飘香食品淘宝 编辑:程序博客网 时间:2024/06/05 02:38
// 相较上一版本改进// 1. 修改Bug//当设置每月一次频率时,设置的Day日期如果为31,30,29,在有些年份的有些月份会抛出异常,因为有些月份是没有31天的,改正之后,//如果设置了31天,则只有有31天的月份会执行。// 2. 修正一年中某天的日期较验功能。// 3. 新增加循环模式//每个月最后一天执行一次。// 4. 支持到秒的定时

using System;using System.Text;using System.Windows.Forms;using UpSoft.Framework.CommonFunction.WinService;namespace TestProject{/// <summary>/// 测试服务/// </summary>public class TestServices : ServiceTimerControl{/// <summary>/// 服务代码/// </summary>protected override void StartService(){// 需要处理的服务代码}/// <summary>/// 时间配置策略名(可不重写。默认读配置文件中的default)/// </summary>public override string ConfigName { get { return "A"; } }}}要调用时,只需输入以下代码new TestServices().Start();
//时间策略配置,可选择以下两种之一,配置文件,或是重写实现基类的获取时间策略配置//1.代码重写/// <summary>/// 时间策略配置/// </summary>/// <returns></returns>protected override TimerConfig GetTimerConfig(){return new TimerConfig{ TimerMode=..., ...};}//2.配置文件实现
<?xml version="1.0" encoding="utf-8"?><configuration><configSections><section name="ServiceTimerConfig" type="UpSoft.Framework.CommonFunction.WinService.ServiceTimerConfigManager,CommonFunction"></section></configSections><ServiceTimerConfig><!--默认采用策略--><Default>A</Default><!--A配置项(全节点)--><Config><!--A策略--><RefName>A</RefName><TimerMode>Interval</TimerMode><!--延迟开始处理(单位毫秒)可为空--><Delay>10000</Delay><!--文件生成时间间隔(单位毫秒,1800000=30分钟)--><Interval>600000</Interval><!--月份--><MonthSeq></MonthSeq><!--指定第几天的序号--><DaySeq></DaySeq><!--定时配置--><Times><!--一天之中需要执行任务的时间点--><TimeValue>11:20:19</TimeValue><TimeValue>10:10:43</TimeValue><TimeValue>19:10:28</TimeValue></Times></Config><!--B配置项(轮询策略)--><Config><!--B策略,每隔设置的时间执行一次--><RefName>B</RefName><TimerMode>Interval</TimerMode><!--延迟开始处理(单位毫秒)--><Delay>10000</Delay><!--文件生成时间间隔(单位毫秒,1800000=30分钟)--><Interval>600000</Interval></Config><!--C配置项(天设置)--><Config><!--C策略,每周4在配置的时间点上执行--><RefName>C</RefName><TimerMode>Week</TimerMode><!--延迟开始处理(单位毫秒)--><Delay>10000</Delay><!--每周的星期四的以下时间执行--><DaySeq>4</DaySeq><!--定时配置--><Times><!--一天之中需要执行任务的时间点--><TimeValue>11:20:19</TimeValue><TimeValue>10:10:43</TimeValue><TimeValue>19:10:28</TimeValue></Times></Config><!--D配置项(月、天设置)--><Config><!--D策略,每年12月8号在配置的时间点上执行--><RefName>D</RefName><TimerMode>Month</TimerMode><!--延迟开始处理(单位毫秒)--><Delay>10000</Delay><!--月份--><MonthSeq>12</MonthSeq><!--天数--><DaySeq>8</DaySeq><!--定时配置--><Times><!--一天之中需要执行任务的时间点--><TimeValue>11:20:19</TimeValue><TimeValue>10:10:43</TimeValue><TimeValue>19:10:28</TimeValue></Times></Config></ServiceTimerConfig></configuration>// TimerMode的定义public enum TimerMode{////// 轮询方式/// Interval = 0,/// /// 一个月中某个天数的指定时间/// Month = 1,/// /// 一周中的周几的指定时间/// Week = 2,/// /// 一天中的指定时间/// Day = 3,/// /// 一年中第几天的指定时间/// Year = 4,/// /// 一年中的指定日期的指定时间/// Date = 5,/// /// 每个月倒数第N天/// LastDayOfMonth/// /// 未设置/// NoSet}

以下是组件的源代码

using System;using System.Collections.Generic;using System.Configuration;using System.Text.RegularExpressions;using System.Threading;using System.Xml;namespace UpSoft.Framework.CommonFunction.WinService{/// <summary>/// 服务定时器管理/// </summary>public abstract class ServiceTimerControl{#region 私有成员/// <summary>/// 定时器/// </summary>private Timer SysTimer { get; set; }/// <summary>/// 是否启用定时器/// </summary>private bool _EnabledTimer = true;/// <summary>/// 服务执行状态, 0-休眠, 1-运行/// </summary>private int _serviceStatus = 0;#endregion#region 公共属性/// <summary>/// 获取服务状态/// </summary>public int ServiceStatus { get { return _serviceStatus; } }/// <summary>/// 定时器配置/// </summary>public TimerConfig Config { get; set; }/// <summary>/// 时间计算类/// </summary>public TimerControl TimerControl { get; set; }/// <summary>/// 配置名称/// </summary>public virtual string ConfigName { get { return ( ServiceTimerConfigManager.ServiceConfig == null ? "" : ServiceTimerConfigManager.ServiceConfig.Default ); } }#endregion/// <summary>/// 停止/// </summary>public void Stop(){_EnabledTimer = false;if ( SysTimer != null ) SysTimer.Change( Timeout.Infinite, Timeout.Infinite );}/// <summary>/// 开始服务/// </summary>public void Start(){try{_EnabledTimer = true;Config = this.GetTimerConfig();if ( Config.Delay == null )Config.Delay = new TimeSpan( 0 );SysTimer = new Timer( new TimerCallback( this.TimerProcess ), AppDomain.CurrentDomain, Config.Delay, this.Config.Interval );this.Logger( LogLevel.INFO, "服务启动成功!" );}catch ( Exception ex ){this.ServiceException( ex );}}/// <summary>/// 单次执行服务程序/// </summary>public void Process(){try{//开始处理服务this.StartService();}catch ( Exception ex ) { this.ServiceException( ex ); }// 处理服务执行过程中出现的异常}/// <summary>/// 处理间隔服务/// </summary>/// <param name="sender"></param>private void TimerProcess( object sender ){if ( !_EnabledTimer ) return;bool TimeIsUp = true;if ( this.Config.TimerMode != TimerMode.Interval ){// 如果定时方式不是定时轮询的话,就构造TimerControl类,该类用来计算每次执行完程序后// 到下次执行服务时需要休眠的时间try{this.TimerControl = new TimerControl( this.Config );TimeIsUp = this.TimerControl.TimeIsUp;// 获取是否到了执行服务程序的时间了}catch ( Exception ex ){// 读取配置出错且TimerControl对象已不存在,则再抛出异常// 如果上一次读取配置成功,那就就算这次的配置有问题,则也不会停止程序的运行,仍用上一次的数据做为参数if ( this.TimerControl == null ) throw ex;}}try{if ( TimeIsUp )// 时间到了可以执行程序了{// 服务运行了_serviceStatus = 1;// 设置计时器,在无穷时间后再启用(实际上就是永远不启动计时器了--停止计时器计时)SysTimer.Change( Timeout.Infinite, Timeout.Infinite );//开始处理服务this.StartService();}}catch ( Exception ex ) { this.ServiceException( ex ); }// 处理服务执行过程中出现的异常finally{// 如果计时器不为空,则重新设置休眠的时间if ( SysTimer != null ){if ( this.Config.TimerMode == TimerMode.Interval )// 定时轮询设置{// 重新启用计时器SysTimer.Change( this.Config.Interval, this.Config.Interval );}else// 定时设置{// 用cft类计算下一次到期的时间TimeSpan Interval = this.TimerControl.GetNextTimeUp();// 重新启用计时器SysTimer.Change( Interval, Interval );}}_serviceStatus = 0;}}/// <summary>/// 开始服务/// </summary>protected abstract void StartService();/// <summary>/// 记录日志/// </summary>/// <param name="level">错误级别</param>/// <param name="msg"></param>protected virtual void Logger( LogLevel level, string msg ) { return; }/// <summary>/// 定时器初始化/// </summary>protected virtual TimerConfig GetTimerConfig(){var config = ServiceTimerConfigManager.ServiceConfig;if ( config != null && config.Config.Length > 0 ){// 如果没有配置则默认为第1个if ( String.IsNullOrEmpty( ConfigName ) )return config.Config[0];else// 返回配置项foreach ( var c in config.Config ) if ( String.Compare( c.RefName, ConfigName, true ) == 0 ) return c;}throw new Exception( "时间策略配置不正确!" );}/// <summary>/// 系统服务错误/// </summary>/// <param name="ex"></param>protected virtual void ServiceException( Exception ex ) { this.Logger( LogLevel.ERROR, "服务异常:" + ex.Message + " \r\n堆栈:" + ex.StackTrace ); }}#region 定时服务休眠计算类/// <summary>/// 文件生成时间配置/// </summary>public class TimerControl{#region 私有成员private TimerConfig Config { get; set; }#endregion#region 公共成员方法/// <summary>/// 构造函数/// </summary>/// <param name="config">配置参数</param>/// </param>public TimerControl( TimerConfig config ){Config = config;if ( Config == null ) throw new Exception( "定时器时间配置异常!" );switch ( Config.TimerMode ){case TimerMode.Date:if ( Config.MonthSeq < 1 || Config.MonthSeq > 12 )throw new Exception( "定时器时间配置异常(月份取值只能是1~12)!" );var dt = new DateTime( 2012, Config.MonthSeq, 1 );// 之所以选2012,是因为他是闰年,因此2月有29天。var lastDay = GetLastDayByMonth( dt );if ( Config.DaySeq < 1 || Config.DaySeq > lastDay )throw new Exception( "定时器时间配置异常(" + Config.MonthSeq + "月份的天数取值只能是1~" + lastDay + ")!" );break;case TimerMode.Day: break;case TimerMode.Month:if ( Config.DaySeq < 1 || Config.DaySeq > 31 )throw new Exception( "定时器时间配置异常(天数取值只能是1~31)!" );break;case TimerMode.Week:if ( Config.DaySeq < 0 || Config.DaySeq > 6 )throw new Exception( "定时器时间配置异常(星期取值只能是0~6)!" );break;case TimerMode.LastDayOfMonth:if ( Config.DaySeq != 0 ){// 如果等于0的话,表示是每个月的最后一天。if ( Config.DaySeq < 1 || Config.DaySeq > 28 )throw new Exception( "定时器时间配置异常(倒数的天数只能是1~28,即倒数的第1天,第2天。。。有些月份并没有29.30.31天,因此最大只允许倒数第28天)!" );Config.DaySeq -= 1;}break;case TimerMode.Year:if ( Config.DaySeq < 1 || Config.DaySeq > 366 )throw new Exception( "定时器时间配置异常(天数取值只能是1~366)!" );break;}}/// <summary>/// 判断时间是否到了/// </summary>/// <returns>true时间已经到了,false时间还未到</returns>public bool TimeIsUp{get{DateTime dt = DateTime.Now;if ( CheckTimeIsUp( dt.TimeOfDay ) ){switch ( Config.TimerMode ){case TimerMode.Day: return true;case TimerMode.Date: return dt.Month == Config.MonthSeq && dt.Day == Config.DaySeq;case TimerMode.Week: return ( ( int )dt.DayOfWeek ) == Config.DaySeq;case TimerMode.Month: return dt.Day == Config.DaySeq;case TimerMode.Year: return dt.DayOfYear == Config.DaySeq;case TimerMode.LastDayOfMonth: return dt.Day == ( GetLastDayByMonth( dt ) - Config.DaySeq );default: return false;}}elsereturn false;}}/// <summary>/// 时间是否到了/// </summary>/// <returns></returns>private bool CheckTimeIsUp( TimeSpan time ){var tmp = new TimeSpan( time.Hours, time.Minutes, time.Seconds );if ( Config.Times == null )return ( tmp.Ticks == 0 );else{foreach ( var t in Config.Times ){if ( t == tmp ) return true;}return false;}}/// <summary>/// 从现在起到下次时间到还有多少时间/// </summary>/// <returns>时间间隔</returns>public TimeSpan GetNextTimeUp(){///目标时间DateTime _NextDateTime = this.GetNextDateTime();// 保存下一次要执行的时间return _NextDateTime - DateTime.Now;}/// <summary>/// 获取下一次指定配置的时间是多少/// </summary>/// <returns></returns>public DateTime GetNextDateTime(){var time = GetNextTimeConfig();DateTime dt = DateTime.Now;DateTime now, target;switch ( Config.TimerMode ){case TimerMode.Day:#region 每天指定某时执行一次now = new DateTime( 1, 1, 1, dt.Hour, dt.Minute, dt.Second );target = new DateTime( 1, 1, 1, time.Hours, time.Minutes, time.Seconds );if ( now.Ticks >= target.Ticks ) dt = dt.AddDays( 1.0 );//如果当前时间小于指定时刻,则不需要加天dt = new DateTime( dt.Year, dt.Month, dt.Day, time.Hours, time.Minutes, time.Seconds );#endregionbreak;case TimerMode.Month:#region 每月指定某天某时执行一次now = new DateTime( 1, 1, dt.Day, dt.Hour, dt.Minute, dt.Second );target = new DateTime( 1, 1, Config.DaySeq, time.Hours, time.Minutes, time.Seconds );// 1月有31天,所以可以接受任何合法的Day值(因为在赋值时已判断1~31)if ( now.Ticks >= target.Ticks ) dt = dt.AddMonths( 1 );// 当前月份的指定天数执行过了,因此月份加上一个月,当月份加了一个月之后,很可能当前实现的Day值可能会变小(例:3月31号,加上一个月,则日期会变成,4月30号,而不会变成5月1号),// 因此需要判断指定的this.Day是不是比Day大(月份的Day变小的唯一原因是因为月份加了一个月之后,那个月并没有this.Day的天数),如果没有该this.Day的天数。则需要为该月份再加一个月。// 加一个月份,则那下个月一定可以大于等于this.Day, 看看每个月的天数就可以断定了,// 因为没有连着两个月的日期小于等于30的,只有连续两个月是31天。其它就是间隔的出现(this.Day最大只可能为31)// 如此之后,接下来的dt=new DateTime时不为因为dt.Month的月份,因没有this.Day天数而抛异常if ( Config.DaySeq > GetLastDayByMonth( dt ) ) dt = dt.AddMonths( 1 );// 如此是为了确保dt.Month的月份一定有this.Day天(因此如果设置为每个月的31号执行的程序,就只会在1,3,5,7,8,10,12几个月份会执行)dt = new DateTime( dt.Year, dt.Month, Config.DaySeq, time.Hours, time.Minutes, time.Seconds );#endregionbreak;case TimerMode.LastDayOfMonth:#region 每个月倒数第N天的某时某刻执行一次var lastDaybymonth = GetLastDayByMonth( dt ) - Config.DaySeq;now = new DateTime( 1, 1, dt.Day, dt.Hour, dt.Minute, dt.Second );target = new DateTime( 1, 1, lastDaybymonth, time.Hours, time.Minutes, time.Seconds );// 1月有31天,所以可以接受任何合法的Day值(因为在赋值时已判断1~31)if ( now.Ticks >= target.Ticks ){dt = dt.AddMonths( 1 );dt = new DateTime( dt.Year, dt.Month, GetLastDayByMonth( dt ) - Config.DaySeq, time.Hours, time.Minutes, time.Seconds );// 根据新月份求新月份的最后一天。}elsedt = new DateTime( dt.Year, dt.Month, lastDaybymonth, time.Hours, time.Minutes, time.Seconds );#endregionbreak;case TimerMode.Week:#region 每星期指定星期某时执行一次int dow = ( int )dt.DayOfWeek;now = new DateTime( 1, 1, dow + 1, dt.Hour, dt.Minute, dt.Second );target = new DateTime( 1, 1, Config.DaySeq + 1, time.Hours, time.Minutes, time.Seconds );if ( now.Ticks >= target.Ticks )dt = dt.AddDays( Config.DaySeq - dow + 7 );elsedt = dt.AddDays( Config.DaySeq - dow );dt = new DateTime( dt.Year, dt.Month, dt.Day, time.Hours, time.Minutes, time.Seconds );#endregionbreak;case TimerMode.Date:#region 每年指定某月某日某时执行一次now = new DateTime( 4, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second );// 0004年闰年,可以支持2月29.因此选了0004, 这样就不会在构造Target时异常,// 因为比较的关键不在年。所以,只要Now和Target的年份一样就可以,设置成什么年份无所谓target = new DateTime( 4, Config.MonthSeq, Config.DaySeq, time.Hours, time.Minutes, time.Seconds );if ( now.Ticks >= target.Ticks ) dt = dt.AddYears( 1 );if ( Config.MonthSeq == 2 && Config.DaySeq == 29 ){// 因为闰年的最大间隔是8年,平时是4年一闰,可是0096年闰完之后,下一个闰年就是0104年,因此。。。for ( int i = 0; i < 8; i++ )if ( DateTime.IsLeapYear( dt.Year + i ) ){dt = dt.AddYears( i );break;}}dt = new DateTime( dt.Year, Config.MonthSeq, Config.DaySeq, time.Hours, time.Minutes, time.Seconds );#endregionbreak;case TimerMode.Year:#region 每年指定第N天某时执行一次now = new DateTime( 1, 1, 1, dt.Hour, dt.Minute, dt.Second );target = new DateTime( 1, 1, 1, time.Hours, time.Minutes, time.Seconds );if ( dt.DayOfYear > Config.DaySeq || dt.DayOfYear == Config.DaySeq && now.Ticks >= target.Ticks ) dt = dt.AddYears( 1 );dt = dt.AddDays( Config.DaySeq - dt.DayOfYear );dt = new DateTime( dt.Year, dt.Month, dt.Day, time.Hours, time.Minutes, time.Seconds );#endregionbreak;default:throw new Exception( "定时器时间配置异常!" );}return dt;}/// <summary>/// 获取指定日期所在月份的最后一天/// </summary>/// <param name="dt"></param>/// <returns></returns>private int GetLastDayByMonth( DateTime dt ){switch ( dt.Month ){case 4:case 6:case 9:case 11:return 30;case 2:return DateTime.IsLeapYear( dt.Year ) ? 29 : 28;default:return 31;}}/// <summary>/// 获取下一个时间点/// </summary>/// <returns></returns>private TimeSpan GetNextTimeConfig(){if ( Config.Times == null || Config.Times.Length == 0 )return new TimeSpan( 0 );else{var minData = TimeSpan.MaxValue;// 最小时间var minExecData = TimeSpan.MaxValue;// 大于当前时间的最小时间foreach ( var t in Config.Times ){if ( DateTime.Now.TimeOfDay < t && minExecData >= t )// 找出比当前时间大的最小时间minExecData = t;if ( minData > t )// 找出最小的一个时间,当前时间不参与运算minData = t;}if ( minExecData == TimeSpan.MaxValue )// 如果找不到比当前时间大的最小时间,则选择最小时间返回return minData;elsereturn minExecData;}}#endregion}#endregion#region 系统配置实体类&配置读取类/// <summary>/// 时间配置类/// </summary>public class ServiceTimerConfig{/// <summary>/// 默认配置/// </summary>public string Default { get; set; }/// <summary>/// 配置项/// </summary>public TimerConfig[] Config { get; set; }}/// <summary>/// 时间配置/// </summary>public class TimerConfig{/// <summary>/// 配置引用名/// </summary>public string RefName { get; set; }/// <summary>/// 时间模式/// timeMode取值如下:TimerMode.Month、TimerMode.Week、TimerMode.Week、TimerMode.Day、TimerMode.Date、TimerMode.Year/// </summary>public TimerMode TimerMode { get; set; }/// <summary>/// 指定某个时间算法的第几天,第1天就为1/// TimerMode=TimerMode.Month时, 该DaySeq表示每个月中的第几天///TimerMode=TimerMode.Week时, 该DaySeq表示每个星期中的星期几(0-星期天,其它用1-6表示)///TimerMode=TimerMode.Day时, 该值不需要设置///TimerMode=TimerMode.Date时, 该DaySeq表示每个日期中的天数,如:8月12号,则DaySeq为12,MonthSeq为8///TimerMode=TimerMode.LastDayOfMonth时, 该DaySeq表示每个月倒数第几天///TimerMode=TimerMode.Year时, 该DaySeq表示每年中的第几天/// </summary>public int DaySeq { get; set; }/// <summary>/// 当指定一年中某个月的某个日期时有用到如:指定一年中8月12号,则这里的MonthSeq就应该为8/// </summary>public int MonthSeq { get; set; }/// <summary>/// 循环处理时间间隔(单位毫秒)/// </summary>public TimeSpan Interval { get; set; }/// <summary>/// 启动延迟时间(单位毫秒)/// </summary>public TimeSpan Delay { get; set; }/// <summary>/// 时间设置/// </summary>public TimeSpan[] Times { get; set; }}/// <summary>/// 服务处理方法/// </summary>public enum TimerMode{/// <summary>/// 轮询方式/// </summary>Interval = 0,/// <summary>/// 一个月中某个天数的指定时间/// </summary>Month = 1,/// <summary>/// 一周中的周几的指定时间/// </summary>Week = 2,/// <summary>/// 一天中的指定时间/// </summary>Day = 3,/// <summary>/// 一年中第几天的指定时间/// </summary>Year = 4,/// <summary>/// 一年中的指定日期的指定时间/// </summary>Date = 5,/// <summary>/// 每个月倒数第N天/// </summary>LastDayOfMonth,/// <summary>/// 未设置/// </summary>NoSet}/// <summary>/// 读取配置数据/// </summary>public class ServiceTimerConfigManager : IConfigurationSectionHandler{private static Regex regEx = new Regex( @"^(?<h>[01]?\d|2[0-3])(?:[::](?<m>[0-5]\d?))?(?:[::](?<s>[0-5]\d?))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase );/// <summary>/// 请求服务配置/// </summary>public static ServiceTimerConfig ServiceConfig { get; set; }/// <summary>/// 静态构造函数/// </summary>static ServiceTimerConfigManager(){ConfigurationManager.GetSection( "ServiceTimerConfig" );}/// <summary>/// 读取自定义配置节/// </summary>/// <param name="parent">父结点</param>/// <param name="configContext">配置上下文</param>/// <param name="section">配置区</param>/// <returns></returns>object IConfigurationSectionHandler.Create( object parent, object configContext, XmlNode section ){ServiceConfig = new ServiceTimerConfig();var config = new List<TimerConfig>();foreach ( XmlNode node in section.ChildNodes ){if ( node.NodeType == XmlNodeType.Element ){switch ( node.Name.ToLower() ){case "default":ServiceConfig.Default = node.InnerText;break; ;case "config":var tmp = new TimerConfig();SetTimerConfigValue( tmp, node );config.Add( tmp );break; ;}}}ServiceConfig.Config = config.ToArray();return ServiceConfig;}/// <summary>/// 设置定时器值/// </summary>/// <param name="Config"></param>/// <param name="node"></param>private void SetTimerConfigValue( TimerConfig Config, XmlNode node ){int tmp, h, m, s;long longTmp;var times = new List<TimeSpan>();foreach ( XmlNode xn in node.ChildNodes ){if ( xn.NodeType == XmlNodeType.Element ){switch ( xn.Name.ToLower() ){case "refname":Config.RefName = xn.InnerText;break;case "timermode":if ( xn.InnerText != null )Config.TimerMode = ( TimerMode )Enum.Parse( typeof( TimerMode ), xn.InnerText );break;case "delay":Int64.TryParse( xn.InnerText, out longTmp );Config.Delay = new TimeSpan( longTmp * 10 * 1000L );// Delay配置值为毫秒break;case "interval":Int64.TryParse( xn.InnerText, out longTmp );// Interval配置值为毫秒Config.Interval = new TimeSpan( longTmp * 10 * 1000L );break;case "monthseq":// 月份Int32.TryParse( xn.InnerText, out tmp );Config.MonthSeq = tmp;break;case "dayseq":// 指定第几天的序号Int32.TryParse( xn.InnerText, out tmp );Config.DaySeq = tmp;break;case "times"://还是用这个函数处理下一级的配置SetTimerConfigValue( Config, xn );// 设置时间策略break;case "timevalue":var mc = regEx.Match( xn.InnerText );if ( !mc.Success ) throw new Exception( "时间配置不正确!" );Int32.TryParse( mc.Groups["h"].Value, out h );Int32.TryParse( mc.Groups["m"].Value, out m );Int32.TryParse( mc.Groups["s"].Value, out s );times.Add( new TimeSpan( h, m, s ) );break;}}}if ( times.Count != 0 )Config.Times = times.ToArray();}}#endregion}