Question

This question is basically about the custom date formatting. I have managed to develop a countdown application that manages to create an event which later can be viewed, customised or tracked by the customer. The important part is the count down timer that grabs seconds from db and displays the time remaining according to the date formatter. Until now I was using the default setting in app that the counter format was dd:hh:mm, apparently my customer wants to allow the app users to customise the way how the time is being displayed. So now the user can choose four options like yy:weeks:hh:mm or months:weeks:days:hours. I thought the best way would be to save the table four indexes to db as a string. My problem came out with the calendar.

conversionInfo = [sysCalendar components:NSHourCalendarUnit | NSMinuteCalendarUnit | NSDayCalendarUnit | NSSecondCalendarUnit fromDate:today  toDate:future  options:0];

        one = [NSString stringWithFormat:@"%d Days %d Hours %d Minutes %d Seconds %@", [conversionInfo day], [conversionInfo hour],[conversionInfo minute],[conversionInfo second], untilOrSince]; self.eventTimer.text = one;

conversionInfo = [sysCalendar components: NSHourCalendarUnit | NSSecondCalendarUnit fromDate:today  toDate:future  options:0];

        one = [NSString stringWithFormat:@"%ld Seconds %@", (long)[conversionInfo second], untilOrSince];

Also if i do build the calendar with all units but only display seconds for example, then the countdown will only show the 59 seconds and not 343746545 seconds until The counter

These calendar units can't be placed in an array for example, so how would i choose the calendar formatting if user chooses 2,4,5,6 which will be months:days:hours:minutes? I can't say components = [NSArray arrayWithObjects: [arrComponents objectAtIndex:2], [arrComponents objectAtIndex:2],nil];

User options

Any ideas?

Was it helpful?

Solution 3

Thank you for all your answers, I appreciate them.

Before you reply I think of my own solution which may not be as detailed and understood as vikingosegundo but it works and it can be extended.

Firstly from the check table I store the four numbers as a string to database. This allows me to store the formatting info with the "event". Each time i want to get the formatting i needed to get the string and change to array of numbers:

int option = 0;
unsigned int unitFlags = 0;
NSString *input = self.detailItem.prefs;
NSArray *myWords = [input componentsSeparatedByString:@","];

for (int fuu = 0; fuu < myWords.count; fuu++) {

    option = [[myWords objectAtIndex:fuu]intValue];
    switch (option)
    {
        case 0:

            unitFlags = unitFlags | NSYearCalendarUnit;
            break;

        case 1:

            unitFlags = unitFlags | NSMonthCalendarUnit;
            break;

        case 2:

            unitFlags = unitFlags | NSWeekCalendarUnit;
            break;

        case 3:
            unitFlags = unitFlags | NSDayCalendarUnit;
            break;

        case 4:

            unitFlags = unitFlags | NSHourCalendarUnit;
            break;

        case 5:
            unitFlags = unitFlags | NSMinuteCalendarUnit;
            break;
            }
}

As you can see I'm building my unit flag with units according to whats in the array.

Now some how i need to build the calendar, but how do i know whats are the components and the formatting?

Here is the answer:

NSString *prularity = @"";

if ([future compare:today] == NSOrderedAscending ) {

    conversionInfo = [sysCalendar components:unitFlags fromDate:future  toDate:today  options:0];

    untilOrSince = @"since";


} else {

    conversionInfo = [sysCalendar components:unitFlags fromDate:today  toDate:future  options:0];

    untilOrSince = @"until";

}

Here i build my calendar with the selected units, I also count from or to the date.

Finally I just need to do the format according to what was selected:

for (int fuu = 0; fuu < myWords.count; fuu++) {

    option = [[myWords objectAtIndex:fuu]intValue];

    switch (option)
    {
        case 0:

            if ([conversionInfo year] >= 2 || [conversionInfo year] == 0) {
                prularity = @"YEARS";
            } else {
                prularity = @"YEAR";
            }
            one = [one stringByAppendingString:[NSString stringWithFormat:@"%ld %@ ", (long)[conversionInfo year], prularity]];
            break;

        case 1:
            if ([conversionInfo month] >= 2|| [conversionInfo month] == 0) {
                prularity = @"MONTHS";
            } else {
                prularity = @"MONTH";
            }
            one = [one stringByAppendingString:[NSString stringWithFormat:@"%ld %@ ", (long)[conversionInfo month], prularity]];
            break;

        case 2:
            if ([conversionInfo week] >= 2 || [conversionInfo week] == 0) {
                prularity = @"WEEKS";
            } else {
                prularity = @"WEEK";
            }
            one = [one stringByAppendingString:[NSString stringWithFormat:@"%ld %@ ", (long)[conversionInfo week], prularity]];
            break;

        case 3:
            if ([conversionInfo day] >= 2 || [conversionInfo day] == 0) {
                prularity = @"DAYS";
            } else {
                prularity = @"DAY";
            }
            one = [one stringByAppendingString:[NSString stringWithFormat:@"%ld %@ ", (long)[conversionInfo day], prularity]];
            break;

        case 4:
            if ([conversionInfo hour] >= 2 || [conversionInfo hour] == 0) {
                prularity = @"HOURS";
            } else {
                prularity = @"HOUR";
            }
            one = [one stringByAppendingString:[NSString stringWithFormat:@"%ld %@ ", (long)[conversionInfo hour], prularity]];
            break;

        case 5:
            if ([conversionInfo minute] >= 2 || [conversionInfo minute] == 0) {
                prularity = @"MINUTES";
            } else {
                prularity = @"MINUTE";
            }
            one = [one stringByAppendingString:[NSString stringWithFormat:@"%ld %@ ", (long)[conversionInfo minute], prularity]];
            break;
    }

}

And print my counter :

one = [one stringByAppendingString:[NSString stringWithFormat:@" %@",untilOrSince]];
self.eventTimer.text = one;

Hope this will help someone with similar issue. I know this is not the best solution but so far it worked.

OTHER TIPS

OK, that was harder than I initially thought


Throughout the system unit flags are used to define dates as components and so on. This unit flaw can be combined in just one value, a bit mask. I use it for my code.

The year unit might be 0....000010 The month unit might be 0....000100

bitmask for both would be

  0....000010    NSCalendarUnitYear
  0....000100    NSCalendarUnitMonth
 ------------
& 0....000110

So to save the information that one unit is to be used mean we just have to set the appropriate bit to 1

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.selectedUnitsBitmask= 0;
    self.unitNames = @[ @"Year", @"Month", @"Week", @"Day", @"Hour", @"Minute", @"Second"];
    self.units = @[@(NSCalendarUnitYear), @(NSCalendarUnitMonth), @(NSCalendarUnitWeekOfMonth), @(NSCalendarUnitDay), @(NSCalendarUnitHour), @(NSCalendarUnitMinute), @(NSCalendarUnitSecond)];


    NSDateComponents *c = [[NSDateComponents alloc] init];
    c.year = 2014;
    c.month = 12;
    c.day = 25;

    self.futureDate = [[NSCalendar currentCalendar] dateFromComponents:c];
}

The values of self.unitNames are used to populate the table view.
self.units store the unit flag for each available unit.

-(UITableViewCell *)tableView:(UITableView *)tableView
        cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier =@"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    cell.textLabel.text = _unitNames[indexPath.row];


    NSUInteger s;
    NSUInteger row = indexPath.row;
    s = NSIntegerMax & [self.units[row] integerValue];
    cell.accessoryType = (self.selectedUnitsBitmask & s) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
    return cell;

}

If a cell is selected, s = NSIntegerMax & [self.units[row] integerValue]; will check in the bit mask, if this unit is to be used.

If a cell is selected, we want to toggle the bit for the unit it represents

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

    NSUInteger s;
    NSUInteger row = indexPath.row;
    s = NSIntegerMax & [self.units[row] integerValue];
    self.selectedUnitsBitmask ^= s ;
    NSLog(@"%lu, %lu", (unsigned long)s, (unsigned long)self.selectedUnitsBitmask);

    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    cell.accessoryType =(self.selectedUnitsBitmask & s) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
    [self updateCountDownLabel:nil];
}

Here

NSUInteger s;
NSUInteger row = indexPath.row;
s = NSIntegerMax & [self.units[row] integerValue];

s represents the bit that is selected, and

self.selectedUnitsBitmask ^= s ;

will toggle it in the bitmask. bitmask ^= s is the short form of bitmask = bitmask ^ s, where ^ is the exclusive or: the nth bit of the bitmask has to be the opposite of the nth bit of s

  0011    bitmask
^ 0101    s
  ----
= 1100

Now we can update the string that will print the time difference according to the unit flags found in the bitmask:

- (IBAction)updateCountDownLabel:(id)sender {

    BOOL includeYear  = self.selectedUnitsBitmask & NSCalendarUnitYear;
    BOOL includeMonth = self.selectedUnitsBitmask & NSCalendarUnitMonth;
    BOOL includeDay   = self.selectedUnitsBitmask & NSCalendarUnitDay;
    BOOL includeHour  = self.selectedUnitsBitmask & NSCalendarUnitHour;
    BOOL includeMinute= self.selectedUnitsBitmask & NSCalendarUnitMinute;
    BOOL includeSecond= self.selectedUnitsBitmask & NSCalendarUnitSecond;

    NSDateComponents *diffDateComponents = [[NSCalendar currentCalendar] components:self.selectedUnitsBitmask fromDate:[NSDate date] toDate:self.futureDate options:0];

    NSMutableString *outputString = [@"" mutableCopy];
    if (includeYear && diffDateComponents.year)
        [outputString appendFormat:@"%d Year", diffDateComponents.year];
    if (includeMonth && diffDateComponents.month)
        [outputString appendFormat:@" %d Month", diffDateComponents.month];
    if (diffDateComponents.weekOfMonth < NSIntegerMax && diffDateComponents.weekOfMonth)
        [outputString appendFormat:@" %d Week", diffDateComponents.weekOfMonth];
    if (includeDay && diffDateComponents.day)
        [outputString appendFormat:@" %d Day", diffDateComponents.day];
    if (includeHour && diffDateComponents.hour)
        [outputString appendFormat:@" %d Hour", diffDateComponents.hour];
    if (includeMinute && diffDateComponents.minute)
        [outputString appendFormat:@" %d Minute", diffDateComponents.minute];
    if (includeSecond && diffDateComponents.second)
        [outputString appendFormat:@" %d Second", diffDateComponents.second];




    self.countDownLabel.text = [outputString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];

}

To determine if a flag was set, we us the & AND operator

BOOL includeYear  = self.selectedUnitsBitmask & NSCalendarUnitYear;

if the year flag is 0....000010

and the bitmask contains 0....010110

the & operation will return

  0....000010    NSCalendarUnitYear
& 0....010110    bitmask
-------------
= 0....000010    -> NSCalendarUnitYear

we do this for all units exert the week. I have no idea why, but the & operation always return 0 for it. Here we use a hack:

if (diffDateComponents.weekOfYear < NSIntegerMax)
    [outputString appendFormat:@" %d Week", diffDateComponents.weekOfYear];

for new NSDateComponents week is instantiated with the biggest number representable with an integer, NSIntegerMax. If its value is less that NSIntegerMax we assume it was set and we append it to the string.


Result:

enter image description here


The whole Code in one piece:


#import "ViewController.h"

@interface ViewController () <UITableViewDataSource, UITableViewDelegate>

@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet UILabel *countDownLabel;
@property (nonatomic) NSInteger selectedUnitsBitmask;
@property (strong, nonatomic) NSArray *unitNames;
@property (strong, nonatomic) NSArray *units;
@property (strong, nonatomic) NSDate *futureDate;


- (IBAction)updateCountDownLabel:(id)sender;
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.selectedUnitsBitmask= 0;
    self.unitNames = @[ @"Year", @"Month", @"Week", @"Day", @"Hour", @"Minute", @"Second"];
    self.units = @[@(NSCalendarUnitYear), @(NSCalendarUnitMonth), @(NSCalendarUnitWeekOfMonth), @(NSCalendarUnitDay), @(NSCalendarUnitHour), @(NSCalendarUnitMinute), @(NSCalendarUnitSecond)];


    NSDateComponents *c = [[NSDateComponents alloc] init];
    c.year = 2014;
    c.month = 12;
    c.day = 25;

    self.futureDate = [[NSCalendar currentCalendar] dateFromComponents:c];
}



-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.units count];
}

-(UITableViewCell *)tableView:(UITableView *)tableView
        cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier =@"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    cell.textLabel.text = _unitNames[indexPath.row];

    NSUInteger s;
    NSUInteger row = indexPath.row;
    s = NSIntegerMax & [self.units[row] integerValue];
    cell.accessoryType = (self.selectedUnitsBitmask & s) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;


    return cell;

}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

    NSUInteger s;
    NSUInteger row = indexPath.row;
    s = NSIntegerMax & [self.units[row] integerValue];
    self.selectedUnitsBitmask ^= s ;
    NSLog(@"%lu, %lu", (unsigned long)s, (unsigned long)self.selectedUnitsBitmask);

    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    cell.accessoryType =(self.selectedUnitsBitmask & s) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
    [self updateCountDownLabel:nil];
}

- (IBAction)updateCountDownLabel:(id)sender {

    BOOL includeYear  = self.selectedUnitsBitmask & NSCalendarUnitYear;
    BOOL includeMonth = self.selectedUnitsBitmask & NSCalendarUnitMonth;
    BOOL includeDay   = self.selectedUnitsBitmask & NSCalendarUnitDay;
    BOOL includeHour  = self.selectedUnitsBitmask & NSCalendarUnitHour;
    BOOL includeMinute= self.selectedUnitsBitmask & NSCalendarUnitMinute;
    BOOL includeSecond= self.selectedUnitsBitmask & NSCalendarUnitSecond;

    NSDateComponents *diffDateComponents = [[NSCalendar currentCalendar] components:self.selectedUnitsBitmask fromDate:[NSDate date] toDate:self.futureDate options:0];

    NSMutableString *outputString = [@"" mutableCopy];
    if (includeYear && diffDateComponents.year)
        [outputString appendFormat:@"%d Year", diffDateComponents.year];
    if (includeMonth && diffDateComponents.month)
        [outputString appendFormat:@" %d Month", diffDateComponents.month];
    if (diffDateComponents.weekOfMonth < NSIntegerMax && diffDateComponents.weekOfMonth)
        [outputString appendFormat:@" %d Week", diffDateComponents.weekOfMonth];
    if (includeDay && diffDateComponents.day)
        [outputString appendFormat:@" %d Day", diffDateComponents.day];
    if (includeHour && diffDateComponents.hour)
        [outputString appendFormat:@" %d Hour", diffDateComponents.hour];
    if (includeMinute && diffDateComponents.minute)
        [outputString appendFormat:@" %d Minute", diffDateComponents.minute];
    if (includeSecond && diffDateComponents.second)
        [outputString appendFormat:@" %d Second", diffDateComponents.second];


    self.countDownLabel.text = [outputString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];

}

You could use stringByAppendingString:

NSString formattedDate = @"";
formattedDate = [formattedDate stringByAppendingString:[NSString stringWithFormat:@"%d Days", [conversionInfo day]]];
formattedDate = [formattedDate stringByAppendingString:[NSString stringWithFormat:@"%d Hours", [conversionInfo hour]]];
///....
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top