Rewriting short and long date formatting, both for server and ios. Also loading offline stories when server fails half-way through.

This commit is contained in:
Samuel Clay 2013-10-10 12:58:40 -07:00
parent fe237cb80c
commit a56f44cdef
12 changed files with 169 additions and 45 deletions

View file

@ -611,7 +611,7 @@ def load_single_feed(request, feed_id):
if not include_story_content: if not include_story_content:
del story['story_content'] del story['story_content']
story_date = localtime_for_timezone(story['story_date'], user.profile.timezone) story_date = localtime_for_timezone(story['story_date'], user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(story_date, now) story['short_parsed_date'] = format_story_link_date__short(story_date)
story['long_parsed_date'] = format_story_link_date__long(story_date, now) story['long_parsed_date'] = format_story_link_date__long(story_date, now)
if usersub: if usersub:
story['read_status'] = 1 story['read_status'] = 1
@ -684,6 +684,9 @@ def load_single_feed(request, feed_id):
# import random # import random
# time.sleep(random.randint(0, 3)) # time.sleep(random.randint(0, 3))
# if page == 2:
# assert False
return data return data
def load_feed_page(request, feed_id): def load_feed_page(request, feed_id):
@ -797,7 +800,7 @@ def load_starred_stories(request):
for story in stories: for story in stories:
story_date = localtime_for_timezone(story['story_date'], user.profile.timezone) story_date = localtime_for_timezone(story['story_date'], user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(story_date, now) story['short_parsed_date'] = format_story_link_date__short(story_date)
story['long_parsed_date'] = format_story_link_date__long(story_date, now) story['long_parsed_date'] = format_story_link_date__long(story_date, now)
starred_date = localtime_for_timezone(story['starred_date'], user.profile.timezone) starred_date = localtime_for_timezone(story['starred_date'], user.profile.timezone)
story['starred_date'] = format_story_link_date__long(starred_date, now) story['starred_date'] = format_story_link_date__long(starred_date, now)
@ -960,7 +963,7 @@ def load_river_stories__redis(request):
story['story_hash'] not in unread_feed_story_hashes): story['story_hash'] not in unread_feed_story_hashes):
story['read_status'] = 1 story['read_status'] = 1
story_date = localtime_for_timezone(story['story_date'], user.profile.timezone) story_date = localtime_for_timezone(story['story_date'], user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(story_date, now) story['short_parsed_date'] = format_story_link_date__short(story_date)
story['long_parsed_date'] = format_story_link_date__long(story_date, now) story['long_parsed_date'] = format_story_link_date__long(story_date, now)
if story['story_hash'] in starred_stories: if story['story_hash'] in starred_stories:
story['starred'] = True story['starred'] = True

View file

@ -125,8 +125,8 @@ def load_social_stories(request, user_id, username=None):
story['social_user_id'] = social_user_id story['social_user_id'] = social_user_id
# story_date = localtime_for_timezone(story['story_date'], user.profile.timezone) # story_date = localtime_for_timezone(story['story_date'], user.profile.timezone)
shared_date = localtime_for_timezone(story['shared_date'], user.profile.timezone) shared_date = localtime_for_timezone(story['shared_date'], user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(shared_date, now) story['short_parsed_date'] = format_story_link_date__short(shared_date)
story['long_parsed_date'] = format_story_link_date__long(shared_date, now) story['long_parsed_date'] = format_story_link_date__long(shared_date)
story['read_status'] = 1 story['read_status'] = 1
if (read_filter == 'all' or query) and socialsub: if (read_filter == 'all' or query) and socialsub:
@ -279,7 +279,7 @@ def load_river_blurblog(request):
if story['story_hash'] not in unread_feed_story_hashes: if story['story_hash'] not in unread_feed_story_hashes:
story['read_status'] = 1 story['read_status'] = 1
story_date = localtime_for_timezone(story['story_date'], user.profile.timezone) story_date = localtime_for_timezone(story['story_date'], user.profile.timezone)
story['short_parsed_date'] = format_story_link_date__short(story_date, now) story['short_parsed_date'] = format_story_link_date__short(story_date)
story['long_parsed_date'] = format_story_link_date__long(story_date, now) story['long_parsed_date'] = format_story_link_date__long(story_date, now)
if story['story_hash'] in starred_stories: if story['story_hash'] in starred_stories:
story['starred'] = True story['starred'] = True

View file

@ -17,6 +17,7 @@
NSString *storyTitle; NSString *storyTitle;
NSString *storyAuthor; NSString *storyAuthor;
NSString *storyDate; NSString *storyDate;
NSInteger storyTimestamp;
int storyScore; int storyScore;
BOOL isStarred; BOOL isStarred;
BOOL isShared; BOOL isShared;
@ -44,6 +45,7 @@
@property (nonatomic) NSString *storyTitle; @property (nonatomic) NSString *storyTitle;
@property (nonatomic) NSString *storyAuthor; @property (nonatomic) NSString *storyAuthor;
@property (nonatomic) NSString *storyDate; @property (nonatomic) NSString *storyDate;
@property (nonatomic) NSInteger storyTimestamp;
@property (nonatomic) UIColor *feedColorBar; @property (nonatomic) UIColor *feedColorBar;
@property (nonatomic) UIColor *feedColorBarTopBorder; @property (nonatomic) UIColor *feedColorBarTopBorder;

View file

@ -23,6 +23,7 @@ static UIFont *indicatorFont = nil;
@synthesize storyTitle; @synthesize storyTitle;
@synthesize storyAuthor; @synthesize storyAuthor;
@synthesize storyDate; @synthesize storyDate;
@synthesize storyTimestamp;
@synthesize storyScore; @synthesize storyScore;
@synthesize siteTitle; @synthesize siteTitle;
@synthesize siteFavicon; @synthesize siteFavicon;
@ -223,7 +224,8 @@ static UIFont *indicatorFont = nil;
} }
paragraphStyle.alignment = NSTextAlignmentRight; paragraphStyle.alignment = NSTextAlignmentRight;
[cell.storyDate NSString *date = [Utilities formatShortDateFromTimestamp:cell.storyTimestamp];
[date
drawInRect:CGRectMake(leftMargin + (rect.size.width) / 2 - 10, storyAuthorDateY, (rect.size.width) / 2 + 10, 15.0) drawInRect:CGRectMake(leftMargin + (rect.size.width) / 2 - 10, storyAuthorDateY, (rect.size.width) / 2 + 10, 15.0)
withAttributes:@{NSFontAttributeName: font, withAttributes:@{NSFontAttributeName: font,
NSForegroundColorAttributeName: textColor, NSForegroundColorAttributeName: textColor,

View file

@ -301,7 +301,8 @@
- (void)beginOfflineTimer { - (void)beginOfflineTimer {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
if (!appDelegate.storyLocationsCount && self.feedPage == 1 && !self.isOffline) { if (!appDelegate.storyLocationsCount && !self.pageFinished &&
self.feedPage == 1 && !self.isOffline) {
self.isShowingOffline = YES; self.isShowingOffline = YES;
self.isOffline = YES; self.isOffline = YES;
[self showLoadingNotifier]; [self showLoadingNotifier];
@ -381,13 +382,11 @@
if (request.isCancelled) { if (request.isCancelled) {
NSLog(@"Cancelled"); NSLog(@"Cancelled");
return; return;
} else if (self.feedPage == 1) { } else {
self.isOffline = YES; self.isOffline = YES;
self.feedPage = 1;
[self loadOfflineStories]; [self loadOfflineStories];
[self showOfflineNotifier]; [self showOfflineNotifier];
} else {
[self informError:[request error]];
self.pageFinished = YES;
} }
[self.storyTitlesTable reloadData]; [self.storyTitlesTable reloadData];
}]; }];
@ -574,15 +573,12 @@
if (request.isCancelled) { if (request.isCancelled) {
NSLog(@"Cancelled"); NSLog(@"Cancelled");
return; return;
} else if (self.feedPage == 1) { } else {
self.isOffline = YES; self.isOffline = YES;
self.isShowingOffline = NO; self.isShowingOffline = NO;
self.feedPage = 1;
[self loadOfflineStories]; [self loadOfflineStories];
[self showOfflineNotifier]; [self showOfflineNotifier];
} else {
[self informError:[request error]];
self.pageFinished = YES;
[self.storyTitlesTable reloadData];
} }
}]; }];
[request setCompletionBlock:^(void) { [request setCompletionBlock:^(void) {
@ -604,12 +600,11 @@
NSLog(@"Cancelled"); NSLog(@"Cancelled");
return; return;
} else if ([request responseStatusCode] >= 500) { } else if ([request responseStatusCode] >= 500) {
if (self.feedPage == 1) {
self.isOffline = YES; self.isOffline = YES;
self.isShowingOffline = NO; self.isShowingOffline = NO;
self.feedPage = 1;
[self loadOfflineStories]; [self loadOfflineStories];
[self showOfflineNotifier]; [self showOfflineNotifier];
}
if ([request responseStatusCode] == 503) { if ([request responseStatusCode] == 503) {
[self informError:@"In maintenance mode"]; [self informError:@"In maintenance mode"];
self.pageFinished = YES; self.pageFinished = YES;
@ -904,6 +899,7 @@
cell.storyTitle = [title stringByDecodingHTMLEntities]; cell.storyTitle = [title stringByDecodingHTMLEntities];
cell.storyDate = [story objectForKey:@"short_parsed_date"]; cell.storyDate = [story objectForKey:@"short_parsed_date"];
cell.storyTimestamp = [[story objectForKey:@"story_timestamp"] integerValue];
cell.isStarred = [[story objectForKey:@"starred"] boolValue]; cell.isStarred = [[story objectForKey:@"starred"] boolValue];
cell.isShared = [[story objectForKey:@"shared"] boolValue]; cell.isShared = [[story objectForKey:@"shared"] boolValue];

View file

@ -1922,7 +1922,7 @@
NSMutableDictionary *newStory = [story mutableCopy]; NSMutableDictionary *newStory = [story mutableCopy];
[newStory setValue:[NSNumber numberWithBool:saved] forKey:@"starred"]; [newStory setValue:[NSNumber numberWithBool:saved] forKey:@"starred"];
if (saved) { if (saved) {
[newStory setValue:[Utilities formatDateFromTimestamp:nil] forKey:@"starred_date"]; [newStory setValue:[Utilities formatLongDateFromTimestamp:nil] forKey:@"starred_date"];
} else { } else {
[newStory removeObjectForKey:@"starred_date"]; [newStory removeObjectForKey:@"starred_date"];
} }

View file

@ -360,6 +360,9 @@
} }
} }
NSString *storyDate = [Utilities formatLongDateFromTimestamp:[[self.activeStory
objectForKey:@"story_timestamp"]
integerValue]];
NSString *storyHeader = [NSString stringWithFormat:@ NSString *storyHeader = [NSString stringWithFormat:@
"<div class=\"NB-header\"><div class=\"NB-header-inner\">" "<div class=\"NB-header\"><div class=\"NB-header-inner\">"
"<div class=\"NB-story-title\">" "<div class=\"NB-story-title\">"
@ -373,7 +376,7 @@
"</div></div>", "</div></div>",
storyUnread, storyUnread,
storyTitle, storyTitle,
[self.activeStory objectForKey:@"long_parsed_date"], storyDate,
storyAuthor, storyAuthor,
storyTags, storyTags,
storyStarred]; storyStarred];

View file

@ -22,6 +22,7 @@ void drawLinearGradient(CGContextRef context, CGRect rect, CGColorRef startColor
+ (void)saveimagesToDisk; + (void)saveimagesToDisk;
+ (UIImage *)roundCorneredImage:(UIImage *)orig radius:(CGFloat)r; + (UIImage *)roundCorneredImage:(UIImage *)orig radius:(CGFloat)r;
+ (NSString *)md5:(NSString *)string; + (NSString *)md5:(NSString *)string;
+ (NSString *)formatDateFromTimestamp:(NSInteger)timestamp; + (NSString *)formatLongDateFromTimestamp:(NSInteger)timestamp;
+ (NSString *)formatShortDateFromTimestamp:(NSInteger)timestamp;
@end @end

View file

@ -154,14 +154,117 @@ static NSMutableDictionary *imageCache;
]; ];
} }
+ (NSString *)formatDateFromTimestamp:(NSInteger)timestamp { + (NSString *)formatLongDateFromTimestamp:(NSInteger)timestamp {
if (!timestamp) timestamp = [[NSDate date] timeIntervalSince1970]; if (!timestamp) timestamp = [[NSDate date] timeIntervalSince1970];
NSDate *date = [NSDate dateWithTimeIntervalSince1970:(double)timestamp]; NSDate *date = [NSDate dateWithTimeIntervalSince1970:(double)timestamp];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; static NSDateFormatter *dateFormatter = nil;
[formatter setDateFormat:@"LLLL, d Y h:mma"]; static NSDateFormatter *todayFormatter = nil;
static NSDateFormatter *yesterdayFormatter = nil;
static NSDateFormatter *formatterPeriod = nil;
return [formatter stringFromDate:date]; NSDate *today = [NSDate date];
NSDateComponents *components = [[NSCalendar currentCalendar]
components:NSIntegerMax
fromDate:today];
[components setHour:0];
[components setMinute:0];
[components setSecond:0];
NSDate *midnight = [[NSCalendar currentCalendar] dateFromComponents:components];
NSDate *yesterday = [NSDate dateWithTimeInterval:-60*60*24 sinceDate:midnight];
if (!dateFormatter || !todayFormatter || !yesterdayFormatter || !formatterPeriod) {
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"EEEE, MMMM d'Sth', y h:mm"];
todayFormatter = [[NSDateFormatter alloc] init];
[todayFormatter setDateFormat:@"'Today', MMMM d'Sth' h:mm"];
yesterdayFormatter = [[NSDateFormatter alloc] init];
[yesterdayFormatter setDateFormat:@"'Yesterday', MMMM d'Sth' h:mm"];
formatterPeriod = [[NSDateFormatter alloc] init];
[formatterPeriod setDateFormat:@"a"];
}
NSString *dateString;
if ([date compare:midnight] == NSOrderedDescending) {
dateString = [NSString stringWithFormat:@"%@%@",
[todayFormatter stringFromDate:date],
[[formatterPeriod stringFromDate:date] lowercaseString]];
} else if ([date compare:yesterday] == NSOrderedDescending) {
dateString = [NSString stringWithFormat:@"%@%@",
[yesterdayFormatter stringFromDate:date],
[[formatterPeriod stringFromDate:date] lowercaseString]];
} else {
dateString = [NSString stringWithFormat:@"%@%@",
[dateFormatter stringFromDate:date],
[[formatterPeriod stringFromDate:date] lowercaseString]];
}
dateString = [dateString stringByReplacingOccurrencesOfString:@"Sth"
withString:[Utilities suffixForDayInDate:date]];
return dateString;
}
+ (NSString *)formatShortDateFromTimestamp:(NSInteger)timestamp {
if (!timestamp) timestamp = [[NSDate date] timeIntervalSince1970];
NSDate *date = [NSDate dateWithTimeIntervalSince1970:(double)timestamp];
static NSDateFormatter *dateFormatter = nil;
static NSDateFormatter *todayFormatter = nil;
static NSDateFormatter *yesterdayFormatter = nil;
static NSDateFormatter *formatterPeriod = nil;
NSDate *today = [NSDate date];
NSDateComponents *components = [[NSCalendar currentCalendar]
components:NSIntegerMax
fromDate:today];
[components setHour:0];
[components setMinute:0];
[components setSecond:0];
NSDate *midnight = [[NSCalendar currentCalendar] dateFromComponents:components];
NSDate *yesterday = [NSDate dateWithTimeInterval:-60*60*24 sinceDate:midnight];
if (!dateFormatter || !todayFormatter || !yesterdayFormatter || !formatterPeriod) {
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"dd LLL y, h:mm"];
todayFormatter = [[NSDateFormatter alloc] init];
[todayFormatter setDateFormat:@"h:mm"];
yesterdayFormatter = [[NSDateFormatter alloc] init];
[yesterdayFormatter setDateFormat:@"'Yesterday', h:mm"];
formatterPeriod = [[NSDateFormatter alloc] init];
[formatterPeriod setDateFormat:@"a"];
}
NSString *dateString;
if ([date compare:midnight] == NSOrderedDescending) {
dateString = [NSString stringWithFormat:@"%@%@",
[todayFormatter stringFromDate:date],
[[formatterPeriod stringFromDate:date] lowercaseString]];
} else if ([date compare:yesterday] == NSOrderedDescending) {
dateString = [NSString stringWithFormat:@"%@%@",
[yesterdayFormatter stringFromDate:date],
[[formatterPeriod stringFromDate:date] lowercaseString]];
} else {
dateString = [NSString stringWithFormat:@"%@%@",
[dateFormatter stringFromDate:date],
[[formatterPeriod stringFromDate:date] lowercaseString]];
}
return dateString;
}
+ (NSString *)suffixForDayInDate:(NSDate *)date {
NSInteger day = [[[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] components:NSDayCalendarUnit fromDate:date] day];
if (day == 11) {
return @"th";
} else if (day % 10 == 1) {
return @"st";
} else if (day % 10 == 2) {
return @"nd";
} else if (day % 10 == 3) {
return @"rd";
} else {
return @"th";
}
} }
@end @end

View file

@ -2606,7 +2606,7 @@
"-all_load", "-all_load",
); );
PRODUCT_NAME = NewsBlur; PRODUCT_NAME = NewsBlur;
PROVISIONING_PROFILE = "A0156932-124B-4F8E-8B93-EE6598D778F0"; PROVISIONING_PROFILE = "EB97D956-BB90-4F2F-9919-F71949B04B3F";
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
"WARNING_CFLAGS[arch=*]" = "-Wall"; "WARNING_CFLAGS[arch=*]" = "-Wall";
}; };
@ -2638,7 +2638,7 @@
"-all_load", "-all_load",
); );
PRODUCT_NAME = NewsBlur; PRODUCT_NAME = NewsBlur;
PROVISIONING_PROFILE = "A0156932-124B-4F8E-8B93-EE6598D778F0"; PROVISIONING_PROFILE = "EB97D956-BB90-4F2F-9919-F71949B04B3F";
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };

View file

@ -5,7 +5,7 @@
#import <SystemConfiguration/SystemConfiguration.h> #import <SystemConfiguration/SystemConfiguration.h>
#import <MobileCoreServices/MobileCoreServices.h> #import <MobileCoreServices/MobileCoreServices.h>
//#define DEBUG 1 #define DEBUG 1
#ifdef DEBUG #ifdef DEBUG
#define BACKGROUND_REFRESH_SECONDS -5 #define BACKGROUND_REFRESH_SECONDS -5

View file

@ -17,25 +17,39 @@ from vendor import reseekfile
# COMMENTS_RE = re.compile('\<![ \r\n\t]*(--([^\-]|[\r\n]|-[^\-])*--[ \r\n\t]*)\>') # COMMENTS_RE = re.compile('\<![ \r\n\t]*(--([^\-]|[\r\n]|-[^\-])*--[ \r\n\t]*)\>')
COMMENTS_RE = re.compile('\<!--.*?--\>') COMMENTS_RE = re.compile('\<!--.*?--\>')
def format_story_link_date__short(date, now=None): def midnight_today():
if not now: now = datetime.datetime.now() return datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
diff = date.date() - now.date()
if diff.days == 0: def midnight_yesterday(midnight=None):
if not midnight:
midnight = midnight_today()
return midnight - datetime.timedelta(days=1)
def beginning_of_this_month():
return datetime.datetime.now().replace(day=1, hour=0, minute=0, second=0, microsecond=0)
def format_story_link_date__short(date):
date = date.replace(tzinfo=None)
midnight = midnight_today()
if date > midnight:
return date.strftime('%I:%M%p').lstrip('0').lower() return date.strftime('%I:%M%p').lstrip('0').lower()
elif diff.days == 1: elif date > midnight_yesterday(midnight):
return 'Yesterday, ' + date.strftime('%I:%M%p').lstrip('0').lower() return 'Yesterday, ' + date.strftime('%I:%M%p').lstrip('0').lower()
else: else:
return date.strftime('%d %b %Y, ') + date.strftime('%I:%M%p').lstrip('0').lower() return date.strftime('%d %b %Y, ') + date.strftime('%I:%M%p').lstrip('0').lower()
def format_story_link_date__long(date, now=None): def format_story_link_date__long(date, now=None):
if not now: now = datetime.datetime.utcnow() if not now:
diff = now.date() - date.date() now = datetime.datetime.now()
date = date.replace(tzinfo=None)
midnight = midnight_today()
parsed_date = DateFormat(date) parsed_date = DateFormat(date)
if diff.days == 0:
if date > midnight:
return 'Today, ' + parsed_date.format('F jS ') + date.strftime('%I:%M%p').lstrip('0').lower() return 'Today, ' + parsed_date.format('F jS ') + date.strftime('%I:%M%p').lstrip('0').lower()
elif diff.days == 1: elif date > midnight_yesterday(midnight):
return 'Yesterday, ' + parsed_date.format('F jS g:ia').replace('.','') return 'Yesterday, ' + parsed_date.format('F jS g:ia').replace('.','')
elif date.date().timetuple()[7] == now.date().timetuple()[7]: elif date > beginning_of_this_month():
return parsed_date.format('l, F jS g:ia').replace('.','') return parsed_date.format('l, F jS g:ia').replace('.','')
else: else:
return parsed_date.format('l, F jS, Y g:ia').replace('.','') return parsed_date.format('l, F jS, Y g:ia').replace('.','')