我有一堆小时的操作我想确定商店是开放,关闭还是在30,29,28,27 ......我关闭的时间我在Xcode / Objectic-C中这样做。现在我必须这样做,让我们说50个小时的操作。我已经创建了一个函数来执行此操作,但它效率不高并且涉及很多if-else语句。以下是操作小时数
Monday - Thursday
7:30am - Midnight
Friday
7:30am - 10:00pm
Saturday
9:00am - 10:00pm
Sunday
9:00am - Midnight
这是我的功能以及我如何处理它
-(BOOL) dateAndTime:(NSDate*)date getStartDay:(NSInteger)startDay getStartHour:(NSInteger)startHour getStartMin:(NSInteger)startMin getEndDay:(NSInteger)endDay getEndHour:(NSInteger)endHour getEndMin:(NSInteger)endMin{
NSCalendar *calendar = [NSCalendar currentCalendar];
const NSCalendarUnit units = NSWeekdayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit;
NSDateComponents *comps = [calendar components:units fromDate:date];
if (comps.weekday == 1) {
comps.weekday = 7;
}
else comps.weekday = comps.weekday - 2;
NSDate *startOfToday;
[[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit startDate:&startOfToday interval:NULL forDate:date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSTimeZone *timeZone = [NSTimeZone localTimeZone];
[dateFormatter setDateFormat:@"HH:mm"];
[dateFormatter setTimeZone:timeZone];
NSString *dateString = [dateFormatter stringFromDate:date];
NSDate *startDate = [dateFormatter dateFromString:[NSString stringWithFormat:@"%ld:%ld", (long)startHour, (long)startMin]];
NSDate *endDate = [dateFormatter dateFromString:[NSString stringWithFormat:@"%ld:%ld", (long)endHour, (long)endMin]];
NSString *startDateString = [dateFormatter stringFromDate:startDate];
NSString *endDateString = [dateFormatter stringFromDate:endDate];
if ([startDateString compare:dateString] == NSOrderedAscending && [dateString compare:endDateString] == NSOrderedAscending && startDay <= comps.weekday && comps.weekday <= endDay) {
return YES;
}
else return NO;
}
现在我从0-6(0是星期一)开始,然后是24小时的时间。然后像这样使用它:
if ([self dateAndTime:date getStartDay:0 getStartHour:7 getStartMin:30 getEndDay:3 getEndHour:23 getEndMin:30] == YES)
text = @"Open";
else if ([self dateAndTime:date getStartDay:0 getStartHour:23 getStartMin:30 getEndDay:3 getEndHour:24 getEndMin:0] == YES)
text = [NSString stringWithFormat:@"Closes in %@ min", countdownNumber];
else text = @"Closed";
正如你所看到的那样,对于每周的所有日子和每天的小时,它需要大量的if语句并且非常糟糕。仅举一例,它需要8个if-else语句(非常耗时)
现在这个问题的基础是我如何才能更有效率/更好的方法来实现这一目标,而在过去30分钟内能够倒计时?
我做了一些研究,找不到有什么东西倒计时什么时候关闭并且效率很高。
如果您需要该示例,或者想要查看https://gist.github.com/spennyf/b0b18e31c3e9deaa0455
,请参阅该示例的完整if-else语句提前感谢您的帮助和/或建议:)
修改
这是我的.m文件的顶部我有这个
@interface HomeTableViewController () {
ShopHours WeekSchedule[];
}
@end
但是这给了我在评论中谈到的编译错误,如何设置这个变量以便它可以在整个这个.m文件中使用?现在我只是传递它作为功能的额外栏杆,应该没问题。 :)
如果我确定它已经打开,如果该地点在30分钟后关闭,那么设置if语句的最佳方法是什么?如果一个地方在一天中的某些时间/午夜过夜,你可以添加设置。
感谢您的帮助:)
答案 0 :(得分:7)
尝试从不同的方向解决问题。以下面的内容为出发点,并非所有内容都得到解释 - 如果您说不知道词典是什么,那么您应该对其进行研究。
查看问题
你有一个表的开启和关闭时间,检查你的商店是否开放应该是对这个表的查询 - 就像在现实生活中一样#34;。要知道商店是否开放,您需要知道工作日 - 它告诉您要咨询的表格的哪一行,以及时间 - 您与该表格中的两行进行比较。
要在程序中表示一个表,通常使用数组来表示两个相关的时间 - 例如open&amp;关闭时间 - 您可以使用记录,对象或字典等。
你怎么能代表一天中的时间?一般时间&amp;日期计算很复杂,但是在工作日之后你需要知道的是当天的时间,并假设你不担心闰秒(你不是),你可以假设有24小时60分钟一天,所以你可以把时间存储为午夜以来的分钟数 - 给你一个号码。如果您使用自午夜以来的分钟数来确定商店是开放还是关闭,您可以避免复杂的日期比较。
部分代码
由于您的代码已经显示,您可以使用NSCalendar
获取任何NSDate
的星期,小时和分钟。
如何从午夜开始计算小时和分钟到分钟,这是简单的算术,但是你想要做几次,所以可能是一个简单的宏来转换时间小时和分钟:
#define TO_MINUTES(hour, min) (hour * 60 + min)
如何表示开放时间表?
您可以使用NSArray
,按工作日编制索引,其中每个元素都是NSDictionary
,包含两个键/值对 - 用于打开和关闭时间。但是,您的时间只是整数,自午夜以来的分钟数,以及将整数存储在NSDictionary
中需要将它们包装为NSNumber
个对象。 (如果这没有意义,是时候做一些研究了!)
另一种方法是为表使用C风格的数组和结构 - 这样可以很好地工作,因为你只存储整数。
这是一个表示开始和结束时间的C结构定义:
typedef struct
{
NSInteger openTime;
NSInteger closeTime;
} ShopHours;
使用它和上面的宏,您可以轻松定义代表工作时间的常量数组:
ShopHours WeekSchedule[] =
{
{0, 0}, // index 0 - ignore
{ TO_MINUTES(9, 0), TO_MINUTES(24, 0) }, // index 1 - Sunday
{ TO_MINUTES(7, 30), TO_MINUTES(24, 0) }, // index 2 - Monday
...
{ TO_MINUTES(9, 0), TO_MINUTES(22, 0) }, // index 7 - Saturday
};
在实际代码中,您可以从数据文件中读取此内容,但上述全局数组现在可以使用。
(注意,索引0被忽略 - NSDateComponents
编号从星期日开始的1天,数组(包括C风格和NSArray
)从0开始索引,只是忽略第0个元素避免做您的代码中有- 1
。)
您已经拥有将NSDate
分解为NSDateComponents
的代码,使用该代码可以轻松获得午夜以来的工作日和分钟:
NSInteger weekday = comps.weekday;
NSInteger minutes = TO_MINUTES(comps.hour, comps.minute);
使用weekday
为WeekSchedule
表编制索引,并将minutes
与两个条目进行比较,您就完成了,例如商店开放:
if (minutes >= WeekSchedule[weekday].openTime && minutes <= WeekSchedule[weekday].closeTime)
{
// shop is open...
}
else
{
// shop is closed...
}
您可以将上述内容包含在一个方法中,该方法会给出一个日期告诉您商店的状态:
- (NSString *) shopState:(NSDate *)dateAndTime
{
// break out the weekday, hours and minutes...
// your code from the question
NSInteger weekday = comps.weekday;
NSInteger minutes = TO_MINUTES(comps.hour, comps.minute);
if (minutes >= WeekSchedule[weekday].openTime && minutes <= WeekSchedule[weekday].closeTime)
{
// shop is open...
// determine if its closing within 30 mins and return an appropriate string
}
else
{
// shop is closed...
return @"Closed";
}
}
正如您所看到的,此解决方案比您采用的方法短得多。
HTH
附录 - 改进
正如Rob Napier在评论中指出的那样,如果不明显,上述解决方案的概要就是这样,并且省略了诸如商店在午夜开放的情况。以下是您可能需要考虑的一些事项:
商店每天营业时间超过一个时间:有些商店午餐时间关闭,餐厅可能会在午餐和晚上营业等。要处理此事,您需要一份开/关清单每天一次而不是一对。一旦确定了工作日测试,就需要迭代这样的列表。
商店在午夜开放:这只是(1)的一个特例,请考虑一下......
时区:在问题的代码和本答案中的代码中,假设打开/关闭时间和测试时间都来自同一时区。如果您希望支持加拿大某个人确定德国的商店目前是否开放并且可以打电话,您需要考虑时差。
夏令时:这是Rob在评论中提到的极端案例。当发生DST更改时,可能会跳过或重复一小时。如果您支持在那个时间开/关的商店,这只是一个问题 - 在变更期间开放的商店不需要特殊处理。 NSCalendar
将从您测试的时间开始给出正确的小时/分钟,您需要处理打开/关闭时间的任何调整。例如,考虑一家商店在凌晨2点关闭,DST更改从早上2点跳回到凌晨1点,商店是否在凌晨1:30开门营业?是的,它第一次出现,但第二次怎么样?决定这是一个超出时间计算的问题。
您需要决定是否以及如何解决这些问题。
更多提示
好吧,所以它的圣诞节(按照你喜欢的方式 - 吃慢动作的大脑,给礼物的时间等等;) - )
我看到你在另一个问题中问过如何动态创建表而不是使用静态表,所以你可以覆盖它。
让我们考虑多个开放时间的日期&amp;午夜开放:
阵列阵列可以工作,但你可以只保留一整周的开放时间。例如。更改TO_MINUTES
宏以获取日期编号,并将所有时间存储为自周日0000hrs以来的分钟数。现在不是索引数组来找到你迭代或搜索它的那一天 - 数组是有序的,所以你可以根据需要进行二进制搜索,但是一个简单的迭代可能足够快(一周内有多少个打开/关闭期间? )
通过将关闭时间设置为第二天(1),可以涵盖所有但是星期六 - >的午夜时间。太阳,包括在30分钟内完成计算。
处理星期六 - &gt;太阳首先将这段时间分为星期六晚和太阳早晨的部分。将它们添加到您的阵列中,它们将是第一个(周日早上)和最后一个(周六晚上)条目。现在,当您确定关闭时间以确定关闭时间是否为午夜午夜时(例如TO_MINUTES(7, 24, 0)
),如果是,请检查第一个条目的开放时间是否为周日0000小时,如果是,您要调整30分钟检查的结束时间(加上第一期的长度)。
这将处理多个时段并在午夜开放。它不会处理DST,商店假期等 - 你需要决定要处理多少。对于夏令时使用NSTimeZone
来找出时间和时间的变化(不总是1小时),以找出&#34;重复的&#34;并且&#34;失踪&#34;时间 - 但请记住,这只是您商店在这段时间内实际开启/关闭的问题。
新的一年; - )
严重的Rob决定提供几乎完整的代码,但使用Objective-C对象和一些方法,所以我想我会添加我的代码进行比较,因为它引发了有趣的问题。
首先应该注意的是相似性,在算法上两个解决方案是接近的 - 环绕处理方式不同,但任何一种方法都可以这样做,因此不重要。
数据结构的选择存在差异 - 您是否应该将C结构和数组用于此简单或Objective-C对象?框架本身具有许多结构类型 - 例如NSRect
等 - 在Objective-C代码中使用它们没有任何问题。选择不是黑色和白色,有一个灰色区域可能适合,这个问题可能落在那个灰色区域。所以这里有多个开放时间/日解决方案:
// convenience macro
// day 1 = Sunday, ... 7 = Saturday
#define TO_MINUTES(day, hour, min) ((day * 24 + hour) * 60 + min)
#define WEEK_START TO_MINUTES(1, 0, 0)
#define WEEK_FINISH TO_MINUTES(7, 24, 0)
typedef struct
{ NSInteger openTime;
NSInteger closeTime;
} ShopHours;
// Opening hours
ShopHours WeekSchedule[] =
{ { TO_MINUTES(1, 0, 0), TO_MINUTES(1, 0, 15) }, // Sat night special, part of Sat 11:30pm - Sun 0:15am
{ TO_MINUTES(1, 9, 0), TO_MINUTES(1, 24, 0) }, // Sun 9am - Midnight
{ TO_MINUTES(2, 7, 30), TO_MINUTES(2, 24, 0) }, // Mon 7:30am - Midnight
{ TO_MINUTES(3, 7, 30), TO_MINUTES(3, 24, 0) },
{ TO_MINUTES(4, 7, 30), TO_MINUTES(5, 2, 0) }, // Midweek madness, Wed 7:30am - Thursday 2am
{ TO_MINUTES(5, 7, 30), TO_MINUTES(5, 24, 0) },
{ TO_MINUTES(6, 7, 30), TO_MINUTES(6, 22, 0) }, // Fri 7:30am - 10pm
{ TO_MINUTES(7, 9, 0), TO_MINUTES(7, 22, 0) }, // Sat 9am - 10pm
{ TO_MINUTES(7, 23, 30),TO_MINUTES(7, 24, 0) }, // Sat night special, part of Sat 11:30pm - Sun 0:15am
};
- (NSString *) shopState:(NSDate *)dateAndTime
{ NSCalendar *calendar = [NSCalendar currentCalendar];
const NSCalendarUnit units = NSWeekdayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit;
NSDateComponents *comps = [calendar components:units fromDate:dateAndTime];
NSInteger minutes = TO_MINUTES(comps.weekday, comps.hour, comps.minute);
NSLog(@"%ld (%ld, %ld, %ld)", minutes, comps.weekday, comps.hour, comps.minute);
unsigned periods = sizeof(WeekSchedule)/sizeof(ShopHours);
for (unsigned ix = 0; ix < periods; ix++)
{ if (minutes >= WeekSchedule[ix].openTime)
{ if (minutes < WeekSchedule[ix].closeTime)
{
// shop is open, how long till close time?
NSInteger closeTime = WeekSchedule[ix].closeTime;
// handle Sat -> Sun wraparound
if (closeTime == WEEK_FINISH && WeekSchedule[0].openTime == WEEK_START)
closeTime += WeekSchedule[0].closeTime - WEEK_START;
NSInteger closingIn = closeTime - minutes;
if (closingIn <= 30)
return [NSString stringWithFormat:@"Closes in %ld min", closingIn];
else
return @"Open";
}
}
else // minutes < WeekSchedule[ix].openTime
break;
}
return @"Closed";
}
DST问题
我首先虽然这不是一个问题,Rob在评论中提出了这个问题,现在他认为这不是问题,但它不是 - 虽然它可能有点学术性。
如果您不使用NSDate
来表示查询时间,那么这不是问题,Rob的解决方案就是采用这种方式。
原始问题和上面的代码确实使用了NSDate
,并将其作为NSDateComponents
的工作日,小时和分钟分开。考虑由于DST变化凌晨2点变为凌晨1点的情况,商店通常在凌晨1:30关闭。如果您在凌晨1点之前以NSDate
值开头并增加,例如每次增加10分钟,直到您从components:fromDate:
获得大于2的小时值,您将看到如下值:00:50, 01:00,01:10,...,01:50,01:00,01:10,...,01:50,02:00,02:10每次测试这些时间都会报告商店在第一个01:30通过后关闭30分钟,然后重新打开30分钟,直到下一个通过!
如果您以NSDate
开头,只会以工作日/小时/分钟为输入然后不会发生此问题,则只会出现此问题。任何一种方法(结构或对象)都可以以任何方式运行,您只需要决定它是否是您想要解决的问题。
答案 1 :(得分:3)
将您的时间存储为C结构会产生许多内存管理问题。如果可以的话,我会避免这种情况(我认为你可以)。相反,我建议创建一些新类来帮助我们。
首先,我们应该将这些时间视为&#34;一周内的名义时间。&#34;通过这些名义上的时间,我们可以通过在周日&#34;凌晨1点30分&#34;明确地消除DST问题。意味着&#34;我们将在凌晨1:30呼叫的第一个瞬间,无论DST转换可能会发生另一个1:30a。&#34;这就是商店通常如何工作的方式,但在考虑时间功能时要精确起来很重要。
关于&#34;一周内名义时间的好事&#34;我们可以从周日午夜开始计算分钟数,我们知道本周将有60 * 24 * 7(10,080)分钟。我们在这里只关心分钟,所以我们只需要跟踪0到10,079之间的数字。但是我们必须记住,这些数字需要进行模运算(它们可以绕#34;)。询问星期二是在星期三之前还是星期三之后毫无意义。星期二是星期三之前和之前。但是询问星期二星期一(作为起点)和星期三(作为结束点)是否有意义。我们可以确定从星期二开始向前走,我们将在星期三遇到星期一之前遇到。如果这是真的,那就在它们之间。这就是你必须考虑模块化时间的方法。
好的,太多的理论了。让我们看一些代码(所有代码都是here)。首先,我们希望WeekTime
代表一周内的某些时间:
typedef enum {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
} Weekday;
@interface WeekTime : NSObject
@property (nonatomic, readonly) NSInteger minutesSinceStartOfWeek;
- (instancetype)initWithWeekday:(Weekday)weekday hour:(NSInteger)hour minute:(NSInteger)minute;
@end
这一点应该是显而易见的,所以我不会在这里粘贴它。
现在我们要考虑名义周时间的范围。我们制作这些开放范围,因此它们包括开始时间而不是结束时间。如果一个地方以9p关闭,你想要返回&#34;已关闭&#34;在9p,而不是#34;在1米关闭。&#34;
@interface WeekTimeRange : NSObject
@property (nonatomic, readonly) WeekTime *start;
@property (nonatomic, readonly) WeekTime *end;
- (instancetype)initWithStart:(WeekTime *)start end:(WeekTime *)end;
- (BOOL)contains:(WeekTime *)time;
@end
大部分内容应该是显而易见的,但contains:
有一个小技巧。由于我们采用模块化算术,因此start
的合法性更高且更为合适。比end
(记住&#34;更大&#34;并不意味着什么)。这很容易处理:
- (BOOL)contains:(WeekTime *)time {
NSInteger queryMinutes = time.minutesSinceStartOfWeek;
NSInteger startMinutes = self.start.minutesSinceStartOfWeek;
NSInteger endMinutes = self.end.minutesSinceStartOfWeek;
if (startMinutes < endMinutes) {
return (startMinutes <= queryMinutes &&
queryMinutes < endMinutes);
} else {
return (queryMinutes < endMinutes ||
queryMinutes >= startMinutes);
}
}
(在我的例子中,我选择了使start==end
未定义的范围。如果你想为它指定一些含义,要么意味着&#34;所有时间&#34;或&# 34;没有时间,&#34;那么你可能需要对此进行调整。请注意&#34;所有时间,&#34;尽管如此。这真的意味着商店关闭并立即重新开放,所以你将会这样做得到&#34;关闭...&#34;消息。这就是我刚才start==end
未定义的原因。)
好的,最后我们想要一些帮手:
WeekTimeRange *WTMakeRange(Weekday startDay, NSInteger startHour, NSInteger startMinute,
Weekday endDay, NSInteger endHour, NSInteger endMinute) {
return [[WeekTimeRange alloc]
initWithStart:[[WeekTime alloc] initWithWeekday:startDay hour:startHour minute:startMinute]
end:[[WeekTime alloc] initWithWeekday:endDay hour:endHour minute:endMinute]];
}
WeekTimeRange *WTFindOpenRangeIncluding(NSArray *ranges, WeekTime *time) {
for (WeekTimeRange *range in ranges) {
if ([range contains:time]) {
return range;
}
}
return nil;
}
NSString *WTStatus(WeekTimeRange *range, WeekTime *requestedTime) {
if (range != nil) {
NSInteger minutesLeft = range.end.minutesSinceStartOfWeek - requestedTime.minutesSinceStartOfWeek;
if (minutesLeft < 0) {
minutesLeft += [WeekTime maxMinute];
}
if (minutesLeft <= 30) {
return [NSString stringWithFormat:@"Closing in %ld minutes", (long)minutesLeft];
} else {
return @"Open";
}
} else {
return @"Closed";
}
}
这些不需要太多解释。让我们在实践中看到它。您似乎已经知道如何将日期转换为其组件,因此我只是直接创建WeekTime
而不用担心将其转换为NSDate
。
NSArray *shopHours = @[WTMakeRange(Monday, 7, 30, Tuesday, 0, 0),
WTMakeRange(Tuesday, 7, 30, Wednesday, 0, 0),
WTMakeRange(Wednesday, 7, 30, Thursday, 0, 0),
WTMakeRange(Thursday, 7, 30, Friday, 0, 0),
WTMakeRange(Friday, 7, 30, Friday, 22, 0),
WTMakeRange(Saturday, 9, 0, Saturday, 22, 0),
WTMakeRange(Sunday, 9, 0, Monday, 2, 0)
];
WeekTime *mondayElevenPM = [[WeekTime alloc] initWithWeekday:Monday hour:23 minute:00];
WeekTimeRange *openRange = WTFindOpenRangeIncluding(shopHours, mondayElevenPM);
NSString *result = WTStatus(openRange, mondayElevenPM);
NSLog(@"%@", result);
我还没有尝试过大量的测试用例,但我的临时测试似乎有效。所有代码都在gist中。我不测试的一个案例是您的范围是否重叠。上述算法并不关心范围是否有序,但您可能会错误地接近...&#34;消息,如果范围重叠。解决这个问题(通过抛出错误或合并范围)应该是一个非常简单的增强。
答案 2 :(得分:1)
不要让方法dateAndTime包含所有这些参数,而是创建一个自定义日期对象,并使用如下值设置您在该对象上创建的属性:
@interface MyDateModelObject : NSObject
@property (nonatomic, strong) NSDate *startDay;
@property (nonatomic, assign) NSInteger startHour;
And so on...
将dateAndTime方法转换为:
- (BOOL)dateAndTime:(MyDateModelObject *)myDateModelObject
现在进入dateAndTime的代码。这种方法做得太多了。要遵循Single Responsibility Principle我会将像NSDateFormatter和日期比较代码这样的代码移到他们自己的方法中的实用程序方法中。
据说我会更进一步,将dateAndTime重命名为以下内容并在其中添加你的gist代码:
- (NSString *)retrieveStoreState:(MyDateModelObject *)date
现在,在您的代码中,您可以创建一个模型对象并设置它的属性,将其传递给retrieveStoreState,并使其根据您的要点中的逻辑返回状态。
希望这为您提供了一个很好的指导。如果您遵循SRP,您应该能够清理您的代码并使您的要点更加精简,使其更具可读性。