How can I check that NSDate is between two times? - cocoa

I am making a Cocoa application and I want it to be able to conduct a daily update. (this app is for personal use) I want it to be doing its update at a specific time everyday so I set my computer to wake up at a that time. I set a notification observer thing in my app and it will conduct this function if the app gets a computer did awake notification:
- (void) receiveWakeNote: (NSNotification*) note
{
[self conductBeginning];
}
What should I add to make sure that the wake up notice occurred between a specific time, say between 16:00 and 16:15, and then only execute the
[self conductBeginning];
line.

NSDate *now = [NSDate date];
NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit;
NSDateComponents *components = [[NSCalendar currentCalendar] components:units
fromDate:now];
if (components.hour == 16 && components.minute <= 15)
NSLog(#"it's time");

something like this:
NSDate * aDate = ...;
NSDate * bDate = ...;
NSTimeInterval a = [aDate timeIntervalSinceNow];
NSTimeInterval b = [bDate timeIntervalSinceNow];
NSTimeInterval min = a > b ? b : a;
NSTimeInterval max = a < b ? b : a;
NSTimeInterval now = CFAbsoluteTimeGetCurrent();
bool result = now >= min && now <= max;

Related

AVSpeechSynthesizer - How to detect dot for milliseconds pause (iPHONE - iPAD / iOS7 xcode)

I want to read a very long text:
NSString *text =#"Long test with dot. I want speaching pause when read dot. How can I do?";
AVSpeechSynthesizer *synthesizer = [AVSpeechSynthesizer new];
[synthesizer setDelegate:self];
AVSpeechUtterance *utterance = [[AVSpeechUtterance alloc] initWithString:text];
utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:[self.defaults objectForKey:LANGUAGE]];
utterance.rate = 0.28;
[synthesizer speakUtterance:utterance];
I want synthesizer detect dot and pause for 100 millisecond.
Is it possible? How can I do ??
I mean a dot (.)
but maybe I found a solution also fot char ',' :
NSString *text = #"Line one. LineTwo aftre a pause. Line three, pause here again.";
text = [text stringByReplacingOccurrencesOfString:#"," withString:#"."];
NSArray *a = [text componentsSeparatedByString:#"."];
for(NSString *line in a)
{
AVSpeechUtterance *utterance = [[AVSpeechUtterance alloc] initWithString:line];
utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:[self.defaults objectForKey:LINGUA]];
utterance.rate = 0.28;
utterance.**postUtteranceDelay** = 0.3;
[self.synthesizer speakUtterance:utterance];
}

Is there any way to get the application's run time in Cocoa for OS X?

I want to operate with time in my app. What I considered first is system's uptime. Since that looked hard to achieve, I am curious that whether there is a simple and efficient way to get my application's run time?
Better time in miliseconds or timeinterval.
The easiest way to get an approximation of your application's running time would be to store a NSDate when the app delegate method applicationDidFinishLaunching: is called and subtract that date from the current time whenever you need the process running time.
static NSTimeInterval startTime;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
startTime = [NSDate timeIntervalSinceReferenceDate];
}
- (IBAction)printRunningTime:(id)sender
{
NSLog(#"Approximate running interval:%f", [NSDate timeIntervalSinceReferenceDate] - startTime);
}
If you need a more accurate running interval for your PID, you can use sysctl.
This will give you an exact timestamp for the point where the OS considers your process "running" in UNIX time. (If you want the timestamp in your local timezone, you can use a NSDateFormatter as below).
#include <sys/sysctl.h>
#include <sys/types.h>
- (IBAction)printRunningTime:(id)sender
{
pid_t pid = [[NSProcessInfo processInfo] processIdentifier];
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid };
struct kinfo_proc proc;
size_t size = sizeof(proc);
sysctl(mib, 4, &proc, &size, NULL, 0);
NSDate* startTime = [NSDate dateWithTimeIntervalSince1970:proc.kp_proc.p_starttime.tv_sec];
NSLog(#"Process start time for PID:%d in UNIX time %#", pid, startTime);
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterLongStyle];
[dateFormatter setLocale:[NSLocale currentLocale]];
NSLog(#"Process start time for PID:%d in local time %#", pid, [dateFormatter stringFromDate:startTime]);
}

Getting NSDate from JSON generated by WCF REST service

The following code works, but it feels dirty. Is there a more standard way to convert epoch date with offset into an NSDate?
- (NSDate *) dateFromJSONString: (NSString *) JSONString{
//expects JSON from .NET WCF Service in epoch ticks, ex:
//"timeScheduled":"\/Date(1348600140000+0100)\/"
NSString *date = [[JSONString stringByReplacingOccurrencesOfString:#"/Date(" withString:#""] stringByReplacingOccurrencesOfString:#")/" withString:#""];
NSString *offsetString = [date substringFromIndex:(date.length - 5)];
//convert to seconds
NSTimeInterval dateInterval = [date doubleValue] /1000;
//gets offset value in seconds - +0100 -> 100 -> 1 -> 3600
double offsetValue = ([offsetString doubleValue] / 100) * 60 * 60;
if ([[offsetString substringToIndex:1] isEqualToString:#"+"]) {
dateInterval = dateInterval + offsetValue;
}
else{
dateInterval = dateInterval - offsetValue;
}
NSDate *retVal = [[NSDate alloc]initWithTimeIntervalSince1970:dateInterval];
return retVal;
}
Try this
the original gist
#implementation NSDate (DotNetDates)
+(NSDate*) dateFromDotNet:(NSString*)stringDate{
NSDate *returnValue;
if ([stringDate isMemberOfClass:[NSNull class]]) {
returnValue=nil;
}
else {
NSInteger offset = [[NSTimeZone defaultTimeZone] secondsFromGMT];
returnValue= [[NSDate dateWithTimeIntervalSince1970:
[[stringDate substringWithRange:NSMakeRange(6, 10)] intValue]]
dateByAddingTimeInterval:offset];
}
return returnValue;
}
-(NSString*) dateToDotNet{
double timeSince1970=[self timeIntervalSince1970];
NSInteger offset = [[NSTimeZone defaultTimeZone] secondsFromGMT];
offset=offset/3600;
double nowMillis = 1000.0 * (timeSince1970);
NSString *dotNetDate=[NSString stringWithFormat:#"/Date(%.0f%+03d00)/",nowMillis,offset] ;
return dotNetDate;
}
#end
// /Date(-422928000000+0100)/
Docs:
DateTime values appear as JSON strings in the form of "/Date(700000+0500)/", where the first number (700000 in the example provided) is the number of milliseconds in the GMT time zone, regular (non-daylight savings) time since midnight, January 1, 1970. The number may be negative to represent earlier times. The part that consists of "+0500" in the example is optional and indicates that the time is of the Local kind - that is, should be converted to the local time zone on deserialization. If it is absent, the time is deserialized as Utc. The actual number ("0500" in this example) and its sign (+ or -) are ignored.
NSTimeInterval is always specified in seconds; it yields sub-millisecond precision over a range of 10,000 years.
+ (NSDate*) dateFromDotNet:(NSString *)stringDate{
if(stringDate==(id)[NSNull null])
return nil;
NSInteger ix0= [stringDate rangeOfString:#"("].location;
NSInteger ix1= [stringDate rangeOfString:#")"].location;
if(ix0==NSNotFound || ix1==NSNotFound)
#throw [NSException exceptionWithName:#"ExceptionName" reason:#"Invalid JSON data" userInfo:#{#"json":stringDate}];
NSRange range= NSMakeRange(ix0+1, ix1-ix0);
NSString *dateString= [stringDate substringWithRange:range];
// dateString: -422928000000+0100
NSCharacterSet *signs= [NSCharacterSet characterSetWithCharactersInString:#"+-"];
range= [dateString rangeOfCharacterFromSet:signs option:NSBackwardSearch];
// WCF will send 13 digit-long value for the time interval since 1970 (millisecond precision)
// whereas iOS works with 10 digit-long values (second precision), hence the divide by 1000
NSTimeInterval unixTime = [dateString doubleValue] / 1000;
if(range.location!=NSNotFound){
NSString *sign = [dateString substringWithRange:range];
NSString *off = [dateString substringFromIndex:range.location+1];
// gets offset value in seconds -+0100 -> 100 -> 1 -> 3600
double offset = ([off doubleValue] / 100) * 60 * 60;
if ([sign isEqualToString:#"+"])
unixTime+= offset;
else
unixTime-= offset;
}
NSDate *date= [NSDate dateWithTimeIntervalSince1970:unixTime];
return date;
}

What kind of category methods do you use to make Cocoa programming easier?

I use a collection of category methods for Cocoa's built in classes to make my life easier. I'll post some examples, but I really want to see what other coders have come up with. What kind of handy category methods are you using?
Example #1:
#implementation NSColor (MyCategories)
+ (NSColor *)colorWithCode:(long)code
{
return [NSColor colorWithCalibratedRed:((code & 0xFF000000) >> 24) / 255.0
green:((code & 0x00FF0000) >> 16) / 255.0
blue:((code & 0x0000FF00) >> 8) / 255.0
alpha:((code & 0x000000FF) ) / 255.0];
}
#end
// usage:
NSColor * someColor = [NSColor colorWithCode:0xABCDEFFF];
Example #2:
#implementation NSView (MyCategories)
- (id)addNewSubViewOfType:(Class)viewType inFrame:(NSRect)frame
{
id newView = [[viewType alloc] initWithFrame:frame];
[self addSubview:newView];
return [newView autorelease];
}
#end
// usage:
NSButton * myButton = [someView addNewSubviewOfType:[NSButton class]
inFrame:someRect];
I've really been loving Andy Matuschak's "KVO+Blocks" category on NSObject. (Yes, it adds some new classes internally as implementation details, but the end result is just a category on NSObject). It lets you provide a block to be executed when a KVO-conforming value changes rather than having to handle every KVO observation in the observeValueForKeyPath:ofObject:change:context: method.
Regular Expressions with RegexKitLite. Download # RegexKitLite-3.1.tar.bz2
Category, which adds md5/sha1 hashing to NSString. NSData one is similar.
#define COMMON_DIGEST_FOR_OPENSSL
#import <CommonCrypto/CommonDigest.h>
#implementation NSString(GNExtensions)
- (NSString*)
hashMD5
{
NSData* data = [self dataUsingEncoding: NSUTF8StringEncoding allowLossyConversion: NO];
unsigned char hashingBuffer[16];
char outputBuffer[32];
CC_MD5([data bytes], [data length], hashingBuffer);
for(int index = 0; index < 16; index++)
{
sprintf(&outputBuffer[2 * index], "%02x", hashingBuffer[index]);
}
return([NSString stringWithCString: outputBuffer length: 32]);
}
- (NSString*)
hashSHA1
{
NSData* data = [self dataUsingEncoding: NSUTF8StringEncoding allowLossyConversion: NO];
unsigned char hashingBuffer[20];
char outputBuffer[40];
CC_SHA1([data bytes], [data length], hashingBuffer);
for(int index = 0; index < 20; index++)
{
sprintf(&outputBuffer[2 * index], "%02x", hashingBuffer[index]);
}
return([NSString stringWithCString: outputBuffer length: 40]);
}
#end
I have a few nifty methods on NSDate. This is self-explanatory:
-(BOOL)isOnTheSameDayAsDate:(NSDate *)date {
NSCalendar *cal = [NSCalendar currentCalendar];
NSDateComponents *selfComponents = [cal components:NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit
fromDate:self];
NSDateComponents *dateComponents = [cal components:NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit
fromDate:date];
return (([selfComponents day] == [dateComponents day]) &&
([selfComponents month] == [dateComponents month]) &&
([selfComponents year] == [dateComponents year]));
}
Usage:
if ([aDate isOnTheSameDayAsDate:anotherDate]) { ... }
This provides a method to easily get dates like "9am the day before":
-(NSDate *)dateWithDayDelta:(NSInteger)daysBeforeOrAfter atHour:(NSUInteger)hour minute:(NSUInteger)minute second:(NSUInteger)second {
NSDate *date = [self addTimeInterval:(24 * 60 * 60) * daysBeforeOrAfter];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *comps = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit |
NSMinuteCalendarUnit | NSSecondCalendarUnit
fromDate:date];
[comps setHour:hour];
[comps setMinute:minute];
[comps setSecond:second];
return [calendar dateFromComponents:comps];
}
Usage:
// We want 9am yesterday
NSDate *nineAmYesterday = [[NSDate date] dateWithDayDelta:-1
atHour:9
minute:0
second:0];

Fuzzy date algorithm

I'm looking for a fuzzy date algorithm. I just started writing one and realised what a tedious task it is. It quickly degenerated into a lot of horrid code to cope with special cases like the difference between "yesterday", "last week" and "late last month" all of which can (in some cases) refer to the same day but are individually correct based on today's date.
I feel sure there must be an open source fuzzy date formatter but I can't find it. Ideally I'd like something using NSDate (OSX/iPhone) and its formatters but that isn't the difficult bit. Does anyone know of a fuzzy date formatter taking any time period relative to now and returning a string like (but not limited to):
a few moments ago
in the last five minutes
earlier today
this morning
last night
last week
last wednesday
early last month
june last year
a couple of years ago
In an ideal world I'd like the string to be as rich as possible (i.e. returning random variants on "Just a moment ago" such as "just now").
Clarification. I'm looking for something more subtle than basic buckts and strings. I want something that knows "yesterday" and "last wednesday" can both refer to the same period but only one is correct when today is Thursday.
There is a property in NSDateFormatter - "doesRelativeDateFormatting". It appears only in 10.6/iOS4.0 and later but it will format a date into a relative date in the correct locale.
From Apple's Documentation:
If a date formatter uses relative date
formatting, where possible it replaces
the date component of its output with
a phrase—such as “today” or
“tomorrow”—that indicates a relative
date. The available phrases depend on
the locale for the date formatter;
whereas, for dates in the future,
English may only allow “tomorrow,”
French may allow “the day after the
day after tomorrow,” as illustrated in
the following example.
Code
The following is code that will print out a good number of the relative strings for a given locale.
NSLocale *locale = [NSLocale currentLocale];
// NSLocale *locale = [[[NSLocale alloc] initWithLocaleIdentifier:#"fr_FR"] autorelease];
NSDateFormatter *relativeDateFormatter = [[NSDateFormatter alloc] init];
[relativeDateFormatter setTimeStyle:NSDateFormatterNoStyle];
[relativeDateFormatter setDateStyle:NSDateFormatterMediumStyle];
[relativeDateFormatter setDoesRelativeDateFormatting:YES];
[relativeDateFormatter setLocale:locale];
NSDateFormatter *normalDateFormatter = [[NSDateFormatter alloc] init];
[normalDateFormatter setTimeStyle:NSDateFormatterNoStyle];
[normalDateFormatter setDateStyle:NSDateFormatterMediumStyle];
[normalDateFormatter setDoesRelativeDateFormatting:NO];
[normalDateFormatter setLocale:locale];
NSString * lastUniqueString = nil;
for ( NSTimeInterval timeInterval = -60*60*24*400; timeInterval < 60*60*24*400; timeInterval += 60.0*60.0*24.0 )
{
NSDate * date = [NSDate dateWithTimeIntervalSinceNow:timeInterval];
NSString * relativeFormattedString = [relativeDateFormatter stringForObjectValue:date];
NSString * formattedString = [normalDateFormatter stringForObjectValue:date];
if ( [relativeFormattedString isEqualToString:lastUniqueString] || [relativeFormattedString isEqualToString:formattedString] )
continue;
NSLog( #"%#", relativeFormattedString );
lastUniqueString = relativeFormattedString;
}
Notes:
A locale is not required
There are
not that many substitutions for
English. At the time of writing there
are: "Yesterday, Today, Tomorrow".
Apple may include more in the future.
It's fun to change the locale and see
what is available in other languages
(French has a few more than English,
for example)
If on iOS, you might want to subscribe to UIApplicationSignificantTimeChangeNotification
Interface Builder
You can set the "doesRelativeDateFormatting" property in Interface Builder:
Select your NSDateFormatter and
choose the "Identity Inspector" tab
of the Inspector Palette (the last
one [command-6]).
Under the sub-section named "User
Defined Runtime Attributes", you can
add your own value for a key on the selected object (in this case, your NSDateFormatter instance). Add
"doesRelativeDateFormatting", choose
a "Boolean" type, and make sure it's
checked.
Remember: It may look like it didn't work at all, but that might because there are only a few substituted values for your locale. Try at least a date for Yesterday, Today, and Tomorrow before you decide if it's not set up right.
This question should get you started. It has the code this very site uses to calculate its relative time. It may not have the specific ranges you want, but they are easy enough to add once you got it setup.
You might want to look at Rail's distance_of_time_in_words function in date_helper.rb, which I've pasted below.
# File vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb, line 59
def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {})
from_time = from_time.to_time if from_time.respond_to?(:to_time)
to_time = to_time.to_time if to_time.respond_to?(:to_time)
distance_in_minutes = (((to_time - from_time).abs)/60).round
distance_in_seconds = ((to_time - from_time).abs).round
I18n.with_options :locale => options[:locale], :scope => 'datetime.distance_in_words''datetime.distance_in_words' do |locale|
case distance_in_minutes
when 0..1
return distance_in_minutes == 0 ?
locale.t(:less_than_x_minutes, :count => 1) :
locale.t(:x_minutes, :count => distance_in_minutes) unless include_seconds
case distance_in_seconds
when 0..4 then locale.t :less_than_x_seconds, :count => 5
when 5..9 then locale.t :less_than_x_seconds, :count => 10
when 10..19 then locale.t :less_than_x_seconds, :count => 20
when 20..39 then locale.t :half_a_minute
when 40..59 then locale.t :less_than_x_minutes, :count => 1
else locale.t :x_minutes, :count => 1
end
when 2..44 then locale.t :x_minutes, :count => distance_in_minutes
when 45..89 then locale.t :about_x_hours, :count => 1
when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
when 1440..2879 then locale.t :x_days, :count => 1
when 2880..43199 then locale.t :x_days, :count => (distance_in_minutes / 1440).round
when 43200..86399 then locale.t :about_x_months, :count => 1
when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes / 43200).round
when 525600..1051199 then locale.t :about_x_years, :count => 1
else locale.t :over_x_years, :count => (distance_in_minutes / 525600).round
end
end
end
So, here is the category I wrote on NSDate for those who are still interested. The problem is one of those that becomes a little quixotic. It is basically a huge switch statment (although I implemented it in a series of cascading if()s to keep it more readable.
For each time period I then select from a random set of ways of telling the time.
All in all, this delighted a few of our users but I'm not sure it was worth the effort.
NSTimeInterval const kTenSeconds = (10.0f );
NSTimeInterval const kOneMinute = (60.0f);
NSTimeInterval const kFiveMinutes = (5.0f*60.0f);
NSTimeInterval const kFifteenMinutes = (15.0f*60.0f) ;
NSTimeInterval const kHalfAnHour = (30.0f*60.0f) ;
NSTimeInterval const kOneHour = 3600.0f; // (60.0f * 60.0f);
NSTimeInterval const kHalfADay = (3600.0f * 12.0f);
NSTimeInterval const kOneDay = (3600.0f * 24.0f);
NSTimeInterval const kOneWeek = (3600.0f * 24.0f * 7.0f);
#implementation NSDate (Fuzzy)
-(NSString*)fuzzyStringRelativeToNow;
{
static NSArray* secondsStrings;
static NSArray* minuteStrings;
static NSArray* fiveMinuteStrings;
static NSArray* halfHourStrings;
static NSArray* earlyMonthStrings;
NSTimeInterval timeFromNow = [self timeIntervalSinceNow];
if((timeFromNow < 0)) // In the past
{
timeFromNow = - timeFromNow;
if ( (timeFromNow < kTenSeconds))
{
if(!secondsStrings)
{
secondsStrings = [[NSArray arrayWithObjects:#"just now",
//#"a few seconds ago",
//#"right this instant",
#"moments ago",
nil] retain];
}
unsigned int index = random() % ([secondsStrings count] - 1);
return [secondsStrings objectAtIndex:index];
}
if ( (timeFromNow < kOneMinute))
{
if(!minuteStrings)
{
minuteStrings = [[NSArray arrayWithObjects:#"just now",
#"very recently",
#"in the last minute",
nil] retain];
}
unsigned int index = random() % ([minuteStrings count] - 1);
return [minuteStrings objectAtIndex:index];
}
if (timeFromNow < kFiveMinutes)
{
if(!fiveMinuteStrings)
{
fiveMinuteStrings = [[NSArray arrayWithObjects:#"just now",
#"very recently",
//#"in the last minute",
#"a few minutes ago",
//#"in the last five minutes",
nil] retain];
}
unsigned int index = random() % ([fiveMinuteStrings count] - 1);
return [fiveMinuteStrings objectAtIndex:index];
}
if (timeFromNow < kFifteenMinutes)
{
return #"in the last 15 minutes";
}
if (timeFromNow < kHalfAnHour)
{
if(!halfHourStrings)
{
halfHourStrings = [[NSArray arrayWithObjects:#"in the last half hour",
//#"in the last half an hour",
#"in the last 30 minutes",
//#"about half an hour ago",
#"fairly recently",
nil] retain];
}
unsigned int index = random() % ([halfHourStrings count] - 1);
return [halfHourStrings objectAtIndex:index];
}
if (timeFromNow < kOneHour)
{
return #"in the last hour";
}
if ((timeFromNow < (kOneHour + kFiveMinutes)) && (timeFromNow > (kOneHour - kFiveMinutes)))
{
return #"about an hour ago";
}
if((timeFromNow < ((kOneHour*2.0f) + kFiveMinutes ))&& (timeFromNow > ((kOneHour*2.0f) - kFiveMinutes)))
{
return #"a couple of hours ago";
}
// Now we're over an hour, we need to calculate a few specific dates to compare against
NSDate *today = [NSDate date];
NSCalendar *gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
NSUInteger unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
NSDateComponents* todayComponents = [gregorian components:unitFlags fromDate:today];
todayComponents.hour = 12;
NSDate* noonToday = [gregorian dateFromComponents:todayComponents];
NSTimeInterval timeSinceNoonToday = [self timeIntervalSinceDate:noonToday];
if (timeSinceNoonToday > 0) // sometime since noon
{
if (timeSinceNoonToday > kOneHour * 9) // i.e. after 9pm today
return #"earlier tonight";
if (timeSinceNoonToday > kOneHour * 7) // i.e. after 7pm today
return #"earlier this evening";
if (timeSinceNoonToday < kOneHour * 1) // between noon and 1pm
return #"early this afternoon";
return #"this afternoon";
}
NSTimeInterval timeSinceMidnight = kHalfADay -timeSinceNoonToday; // Note sign is reversed.
if ((timeSinceNoonToday < 0) & (timeSinceNoonToday > -kHalfADay)) // between midnight and noon today
{
if (timeSinceMidnight < kFiveMinutes)
return #"around midnight";
if (timeSinceMidnight < kOneHour * 2) // up to 2am
return #"very early this morning";
if (timeSinceMidnight < kOneHour * 5) // up to 5am
return #"early this morning";
else if (timeSinceMidnight < kOneHour * 11)
return #"late this morning";
else
return #"this morning";
}
// NSTimeInterval timeSinceNoonYesterday = timeSinceNoonToday - kOneDay;
// timeSinceMidnight = -timeSinceMidnight;
if (timeSinceMidnight < kOneHour * 24) // not the day before...
{
if (timeSinceMidnight < kFiveMinutes)
return #"around midnight";
if (timeSinceMidnight < kFifteenMinutes)
return #"just before midnight";
if (timeSinceMidnight < kOneHour * 2) // after 10pm
return #"late last night";
if (timeSinceMidnight < kOneHour * 5) // After 7
return #"yesterday evening";
else if (timeSinceMidnight < kOneHour * 7)
return #"yesterday evening"; // after 5pm
else if (timeSinceMidnight < kOneHour * 7)
return #"yesterday evening"; // after 5pm
else if (timeSinceMidnight < kOneHour * 10)
return #"yesterday afternoon"; // after 5pm
else if (timeSinceMidnight < kOneHour * 12)
return #"early yesterday afternoon"; // before 1pm
else if (timeSinceMidnight < kOneHour * 13)
return #"late yesterday morning"; // after 11m
else if (timeSinceMidnight < kOneHour * 17)
return #"yesterday morning";
else
return #"early yesterday morning";
}
NSDateFormatter* formatter = [[[NSDateFormatter alloc] init] autorelease];
int integerSeconds = timeSinceMidnight;
int integerDay = kOneDay;
int secondsIntoDay = integerSeconds % integerDay;
NSString* formatString = #"last %#";
if (timeFromNow < kOneWeek)
{
if (secondsIntoDay < kFifteenMinutes)
formatString = #"around midnight on %#";
//else if (secondsIntoDay < kFifteenMinutes)
// formatString = #"just before midnight on %#";
else if (secondsIntoDay < kOneHour * 2) // after 10pm
formatString = #"late on %# night";
else if (secondsIntoDay < kOneHour * 5) // After 7
formatString = #"on %# evening";
else if (secondsIntoDay < kOneHour * 10)
formatString = #"on %# afternoon"; // after 5pm
else if (secondsIntoDay < kOneHour * 12)
formatString = #"early on %# afternoon"; // before 1pm
else if (secondsIntoDay < kOneHour * 13)
formatString = #"late on %# morning"; // after 11am
else if (secondsIntoDay < kOneHour * 17)
formatString = #"on %# morning";
else if (secondsIntoDay < kOneHour * 24) // not the day before...
formatString = #"early on %# morning";
[formatter setDateFormat:#"EEEE"]; /// EEEE is long format of day of the week see: http://unicode.org/reports/tr35/tr35-6.html#Date_Format_Patterns
return [NSString stringWithFormat:formatString, [formatter stringFromDate: self]];
}
//formatString = #"on %# the week before last";
/*if (secondsIntoDay < kOneHour * 2) // after 10pm
formatString = #"early on %# the week before last";
else if (timeSinceMidnight > kOneHour * 13)
formatString = #"late on %# the week before last"; // after 11m*/
//if (timeFromNow < kOneWeek * 2)
//{
// [formatter setDateFormat:#"EEE"]; /// EEE is short format of day of the week see: http://unicode.org/reports/tr35/tr35-6.html#Date_Format_Patterns
// return [NSString stringWithFormat:formatString, [formatter stringFromDate: self]];
//}
if (timeFromNow < kOneWeek * 2)
{
return #"the week before last";
}
NSDateComponents* myComponents = [gregorian components:unitFlags fromDate:self];
int monthsAgo = myComponents.month - todayComponents.month;
int yearsAgo = myComponents.year - todayComponents.year;
if (yearsAgo == 0)
{
if (monthsAgo == 0)
{
if(myComponents.day > 22)
return #"late this month";
if(myComponents.day < 7)
{
if(!earlyMonthStrings)
{
earlyMonthStrings = [[NSArray arrayWithObjects:#"earlier this month",
//#"at the beginning of the month",
#"early this month",
nil] retain];
}
unsigned int index = random() % ([earlyMonthStrings count] - 1);
return [earlyMonthStrings objectAtIndex:index];
}
return #"earlier this month";
}
if (monthsAgo == 1)
{
if(myComponents.day > 22)
return #"late last month";
if(myComponents.day < 7)
return #"early last month";
return #"last month";
}
formatString = #"in %# this year";
/*if(myComponents.day > 22)
formatString = #"late in %# this year";
if(myComponents.day < 7)
formatString = #"early in %# this year";*/
[formatter setDateFormat:#"MMMM"]; /// MMM is longformat of month see: http://unicode.org/reports/tr35/tr35-6.html#Date_Format_Patterns
return [NSString stringWithFormat:formatString, [formatter stringFromDate: self]];
}
if (yearsAgo == 1)
{
formatString = #"in %# last year";
/*if(myComponents.day > 22)
formatString = #"late in %# last year";
if(myComponents.day < 7)
formatString = #"late in %# last year";*/
[formatter setDateFormat:#"MMM"]; /// MMM is longformat of month see: http://unicode.org/reports/tr35/tr35-6.html#Date_Format_Patterns
return [NSString stringWithFormat:formatString, [formatter stringFromDate: self]];
}
// int daysAgo = integerSeconds / integerDay;
// Nothing yet...
[formatter setDateStyle:kCFDateFormatterMediumStyle];
//[formatter setTimeStyle:kCFDateFormatterShortStyle];
return [NSString stringWithFormat:#"on %#",[formatter stringFromDate: self]];
}
else
if(timeFromNow > 0) // The future
{
AICLog(kErrorLogEntry, #"FuzzyDates: Time marked as in the future: referenced date is %#, local time is %#", self, [NSDate date]);
return #"moments ago";
}
else
return #"right now"; // this seems unlikely.
return [self description]; // should never get here.
}
sorry it took so long to post this...
This is based on code in the Pretty and Humane date & time threads. I added handling for "last Monday, 5pm", because I like that more than x days ago. This handles past and future up to centuries. I am keen on the internationalization aspect so this needs a lot more work eventually. Calculations are in the local time zone.
public static class DateTimePretty
{
private const int SECOND = 1;
private const int MINUTE = 60 * SECOND;
private const int HOUR = 60 * MINUTE;
private const int DAY = 24 * HOUR;
private const int WEEK = 7 * DAY;
private const int MONTH = 30 * DAY;
private const int YEAR = 365;
const string now = "just now";
const string secondsFuture = "in {0} seconds", secondsPast = "{0} seconds ago";
const string minuteFuture = "in about a minute", minutePast = "about a minute ago";
const string minutesFuture = "in about {0} minutes", minutesPast = "about {0} minutes ago";
const string hourFuture = "in about an hour", hourPast = "about an hour ago";
const string hoursFuture = "in about {0} hours", hoursPast = "about {0} hours ago";
const string tomorrow = "tomorrow, {0}", yesterday = "yesterday, {0}";
const string nextDay = "{0}", nextWeekDay = "next {0}", lastDay = "last {0}";
//const string daysFuture = "in about {0} days", daysPast = "about {0} days ago";
const string weekFuture = "in about a week", weekPast = "about a week ago";
const string weeksFuture = "in about {0} weeks", weeksPast = "about {0} weeks ago";
const string monthFuture = "in about a month", monthPast = "about a month ago";
const string monthsFuture = "in about {0} months", monthsPast = "about {0} months ago";
const string yearFuture = "in about a year", yearPast = "about a year ago";
const string yearsFuture = "in about {0} years", yearsPast = "about {0} years ago";
const string centuryFuture = "in about a century", centuryPast = "about a century ago";
const string centuriesFuture = "in about {0} centuries", centuriesPast = "about {0} centuries ago";
/// <summary>
/// Returns a pretty version of the provided DateTime: "42 years ago", or "in 9 months".
/// </summary>
/// <param name="dateTime">DateTime in local time format, not Utc</param>
/// <returns>A pretty string</returns>
public static string GetPrettyDate(DateTime dateTime)
{
DateTime dateTimeNow = DateTime.Now;
bool isFuture = (dateTimeNow.Ticks < dateTime.Ticks);
var ts = isFuture ? new TimeSpan(dateTime.Ticks - dateTimeNow.Ticks) : new TimeSpan(dateTimeNow.Ticks - dateTime.Ticks);
double delta = ts.TotalSeconds;
if (delta < 10)
return now;
if (delta < 1 * MINUTE)
return isFuture ? string.Format(secondsFuture, ts.Seconds) : string.Format(secondsPast, ts.Seconds);
if (delta < 2 * MINUTE)
return isFuture ? minuteFuture : minutePast;
if (delta < 45 * MINUTE)
return isFuture ? string.Format(minutesFuture, ts.Minutes) : string.Format(minutesPast, ts.Minutes);
if (delta < 2 * HOUR)
return isFuture ? hourFuture : hourPast;
if (delta < 7 * DAY)
{
string shortTime = DateTimeFormatInfo.CurrentInfo.ShortTimePattern;
string shortWeekdayTime = "dddd, " + shortTime;
int dtDay = (int) dateTime.DayOfWeek;
int nowDay = (int) dateTimeNow.DayOfWeek;
if (isFuture)
{
if (dtDay == nowDay)
{
if (delta < DAY)
return string.Format(hoursFuture, ts.Hours);
else
return string.Format(nextWeekDay, dateTime.ToString(shortWeekdayTime));
}
else if (dtDay - nowDay == 1 || dtDay - nowDay == -6)
return string.Format(tomorrow, dateTime.ToString(shortTime));
else
return string.Format(nextDay, dateTime.ToString(shortWeekdayTime));
}
else
{
if (dtDay == nowDay)
{
if (delta < DAY)
return string.Format(hoursPast, ts.Hours);
else
return string.Format(lastDay, dateTime.ToString(shortWeekdayTime));
}
else if (nowDay - dtDay == 1 || nowDay - dtDay == -6)
return string.Format(yesterday, dateTime.ToString(shortTime));
else
return string.Format(lastDay, dateTime.ToString(shortWeekdayTime));
}
}
//if (delta < 7 * DAY)
// return isFuture ? string.Format(daysFuture, ts.Days) : string.Format(daysPast, ts.Days);
if (delta < 4 * WEEK)
{
int weeks = Convert.ToInt32(Math.Floor((double) ts.Days / 30));
if (weeks <= 1)
return isFuture ? weekFuture : weekPast;
else
return isFuture ? string.Format(weeksFuture, weeks) : string.Format(weeksPast, weeks);
}
if (delta < 12 * MONTH)
{
int months = Convert.ToInt32(Math.Floor((double) ts.Days / 30));
if (months <= 1)
return isFuture ? monthFuture : monthPast;
else
return isFuture ? string.Format(monthsFuture, months) : string.Format(monthsPast, months);
}
// Switch to days to avoid overflow
delta = ts.TotalDays;
if (delta < 100 * YEAR)
{
int years = Convert.ToInt32(Math.Floor((double) ts.TotalDays / 365.25));
if (years <= 1)
return isFuture ? yearFuture : yearPast;
else
return isFuture ? string.Format(yearsFuture, years) : string.Format(yearsPast, years);
}
else
{
int centuries = Convert.ToInt32(Math.Floor((double) ts.TotalDays / 365.2425));
if (centuries <= 1)
return isFuture ? centuryFuture : centuryPast;
else
return isFuture ? string.Format(centuriesFuture, centuries) : string.Format(centuriesPast, centuries);
}
}
}
I am not sure why you say it would be a horrid coding practice. Each of the return strings are actually a subset of the parent set, so you can quite elegantly do this in a if/elseif chain.
if timestamp < 5sec
"A moment ago"
elseif timestamp < 5min
"Few minutes ago"
elseif timestamp < 12hr && timestamp < noon
"Today Morning"
...
elseif timestamp < 1week
"Few days ago"
elseif timestamp < 1month
"Few weeks ago"
elseif timestamp < 6month
"Few Months ago"
...
else
"Really really long time ago"
In my experience these types of date generators are not "fuzzy" at all. In fact, they are just a bunch of if statements based bands of time. For example, any time less than 30 seconds is "moments ago", 360 to 390 days is "just a year ago", etc. Some of these will use the target date to calculate the special names (June, Wednesday, etc).
Sorry to dash an illusions you had.
needless to say (but i'll say it anyway) don't use a where loop that decrements 365 days per year even on 366 day leap years (or you'll find yourself in the ranks of the Zune developers)
here is a c# version:
http://tiredblogger.wordpress.com/2008/08/21/creating-twitter-esque-relative-dates-in-c/
I know expressing times like this has become quite popular lately, but please considering making it an option to switch been relative 'fuzzy' dates and normal absolute dates.
For example, it's useful to know that a comment was made 5 minutes ago, but it's less useful to tell me comment A was 4 hours ago and comment B was 9 hours ago when it's 11 AM and I'd rather know that comment A was written when someone woke up this morning and comment B was written by someone staying up late (assuming I know they are in my timezone).
--
EDIT: looking closer at your question you seem to have avoided this to some degree by referring to time of day instead of "X ago", but on the other hand, you may be giving a false impression if users are in different time zone, since your "this morning" may be in the middle of the night for the relevant user.
It might be cool to augment the times with relative time of day depending on the other user's timezone, but that assumes that users are willing to supply it and that it's correct.
I was not happy with the solution in the other question. So made my own using the Date time class. IMO, its cleaner. In my tests it worked like as I wanted. Hope this helps someone.
DateTime now = DateTime.Now;
long nowticks = now.Ticks;
long thenticks = dt.Ticks;
long diff = nowticks - thenticks;
DateTime n = new DateTime(diff);
if (n.Year > 1)
{
return n.Year.ToString() + " years ago";
}
else if (n.Month > 1)
{
return n.Month.ToString() + " months ago";
}
else if (n.Day > 1)
{
return n.Day.ToString() + " days ago";
}
else if (n.Hour > 1)
{
return n.Hour.ToString() + " hours ago";
}
else if (n.Minute > 1)
{
return n.Minute.ToString() + " minutes ago";
}
else
{
return n.Second.ToString() + " seconds ago";
}
This is almost always done using a giant switch statement and is trivial to implement.
Keep the following in mind:
Always test for the smallest time span first
Don't forget to keep your strings localizable.
You may find the source from timeago useful. The description of the plugin is "a jQuery plugin that makes it easy to support automatically updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago")."
It's essentially a JavaScript port of Rail's distance_of_time_in_words function crammed into a jQuery plugin.
My company has this .NET library that does some of what you want in that it does very flexible date time parsing (including some relative formats) but it only does non-relative outputs.
Check out Chrono for a Javascript heuristic date parser.
Chrono supports most date and time formats, such as:
Today, Tomorrow, Yesterday, Last Friday, etc
17 August 2013 - 19 August 2013
This Friday from 13:00 - 16.00
5 days ago
Sat Aug 17 2013 18:40:39 GMT+0900 (JST)
2014-11-30T08:15:30-05:30
https://github.com/wanasit/chrono

Resources