This is MODateTextCell.m in view mode; [Download] [Up]
// MODateTextCell.m
//
// by Mike Ferris
// Part of MOKit
// Copyright 1993, all rights reserved.
// ABOUT MOKit
// by Mike Ferris (mike@lorax.com)
//
// MOKit is a collection of useful and general objects. Permission is
// granted by the author to use MOKit in your own programs in any way
// you see fit. All other rights pertaining to the kit are reserved by the
// author including the right to sell these objects as objects, as part
// of a LIBRARY, or as SOURCE CODE. In plain English, I wish to retain
// rights to these objects as objects, but allow the use of the objects
// as pieces in a fully functional program. Permission is also granted to
// redistribute the source code of MOKit for FREE as long as this copyright
// notice is left intact and unchanged. NO WARRANTY is expressed or implied.
// The author will under no circumstances be held responsible for ANY
// consequences from the use of these objects. Since you don't have to pay
// for them, and full source is provided, I think this is perfectly fair.
#import "MOKit/MODateTextCell.h"
#import "MOKit/MOString.h"
#import <objc/objc-runtime.h>
#define CLASS_VERSION 0
#define CLASS_NAME "MODateTextCell"
#define BUNDLE_TYPE "bundle"
#define MOSTRING_CLASS_NAME "MOString"
#define AM_DATE_REGEX_1 "^[ ]*(([0][1-9]|[1][0-2])([0][1-9]|[1-2][0-9]|[3][0-1])([0-9]?[0-9]?[0-9][0-9]))[ ]*$"
#define AM_DATE_REGEX_2 "^[ ]*(([0]?[1-9]|[1][0-2])[-/]([0]?[1-9]|[1-2][0-9]|[3][0-1])[-/]([0-9]?[0-9]?[0-9][0-9]))[ ]*$"
#define AM_DATE_REGEX_3 "^[ ]*(([jJ][aA][nN]?[uU]?[aA]?[rR]?[yY]?|[fF][eE]?[bB]?[rR]?[uU]?[aA]?[rR]?[yY]?|[mM][aA][rR][cC]?[hH]?|[aA][pP][rR]?[iI]?[lL]?|[mM][aA][yY]|[jJ][uU][nN][eE]?|[jJ][uU][lL][yY]?|[aA][uU][gG]?[uU]?[sS]?[tT]?|[sS][eE]?[pP]?[tT]?[eE]?[mM]?[bB]?[eE]?[rR]?|[oO][cC]?[tT]?[oO]?[bB]?[eE]?[rR]?|[nN][oO]?[vV]?[eE]?[mM]?[bB]?[eE]?[rR]?|[dD][eE]?[cC]?[eE]?[mM]?[bB]?[eE]?[rR]?)[ ]*([0]?[1-9]|[1-2][0-9]|[3][0-1])[ ]*[,]?[ ]*([0-9]?[0-9]?[0-9][0-9]))[ ]*$"
#define EU_DATE_REGEX_1 "^[ ]*(([0][1-9]|[1-2][0-9]|[3][0-1])([0][1-9]|[1][0-2])([0-9]?[0-9]?[0-9][0-9]))[ ]*$"
#define EU_DATE_REGEX_2 "^[ ]*(([0]?[1-9]|[1-2][0-9]|[3][0-1])[-/]([0]?[1-9]|[1][0-2])[-/]([0-9]?[0-9]?[0-9][0-9]))[ ]*$"
#define EU_DATE_REGEX_3 "^[ ]*(([0]?[1-9]|[1-2][0-9]|[3][0-1])[ ]*[,]?[ ]*([jJ][aA][nN]?[uU]?[aA]?[rR]?[yY]?|[fF][eE]?[bB]?[rR]?[uU]?[aA]?[rR]?[yY]?|[mM][aA][rR][cC]?[hH]?|[aA][pP][rR]?[iI]?[lL]?|[mM][aA][yY]|[jJ][uU][nN][eE]?|[jJ][uU][lL][yY]?|[aA][uU][gG]?[uU]?[sS]?[tT]?|[sS][eE]?[pP]?[tT]?[eE]?[mM]?[bB]?[eE]?[rR]?|[oO][cC]?[tT]?[oO]?[bB]?[eE]?[rR]?|[nN][oO]?[vV]?[eE]?[mM]?[bB]?[eE]?[rR]?|[dD][eE]?[cC]?[eE]?[mM]?[bB]?[eE]?[rR]?)[ ]*[,]?[ ]*([0-9]?[0-9]?[0-9][0-9]))[ ]*$"
@interface MODateTextCell(Private)
+ (Class)MO_loadClassBundle:(const char *)className;
@end
@implementation MODateTextCell
static id MOStringClass;
+ (Class)MO_loadClassBundle:(const char *)className
// Finds the bundle of the same name as the class, grabs it and loads the
// class from it and returns the named class.
{
char pathBuff[MAXPATHLEN+1];
id classBundle = nil;
Class class = nil;
// Load the bundle
if ((class = objc_lookUpClass(className)) == nil) {
// class is not already loaded... load it.
// Look for the bundle in the main bundle first,
// else try in this class's bundle.
if (![[NXBundle mainBundle] getPath:pathBuff forResource:className
ofType:BUNDLE_TYPE]) {
if (![[NXBundle bundleForClass:[self class]] getPath:pathBuff
forResource:className ofType:BUNDLE_TYPE]) {
NXLogError("[%s loadClassBundle] failed to "
"find %s class bundle.", [self name], className);
return nil;
}
}
classBundle = [[NXBundle allocFromZone:[self zone]]
initForDirectory:pathBuff];
if (!classBundle) {
NXLogError("[%s loadClassBundle] failed to "
"create bundle for class %s.", [self name], className);
return nil;
}
if ((class = [classBundle classNamed:className]) == nil) {
NXLogError("[%s loadClassBundle] failed to "
"load %s class from bundle.", [self name], className);
return nil;
}
}
return class;
}
+ initialize
// Set the version.
{
if (self == objc_lookUpClass(CLASS_NAME)) {
[self setVersion:CLASS_VERSION];
// Load the string class if necessary
MOStringClass = [self MO_loadClassBundle:MOSTRING_CLASS_NAME];
}
return self;
}
- initTextCell:(const char *)aString withRegex:(const char *)re
// We'll ignore any regex expression passed to us, cause we manage our own
// expressions.
{
[super initTextCell:aString withRegex:AM_DATE_REGEX_1];
isEuropeanStyle = NO;
doesFormat = NO;
formatString = nil;
[self addRegexStr:AM_DATE_REGEX_2];
[self addRegexStr:AM_DATE_REGEX_3];
formatString = [[MOStringClass allocFromZone:[self zone]] init];
return self;
}
- MO_copyFormatString:str zone:(NXZone *)zone
{
formatString = [[MOStringClass allocFromZone:zone]
initStringValue:[str stringValue]];
return self;
}
- copyFromZone:(NXZone *)zone
{
id obj = [super copyFromZone:zone];
[obj MO_copyFormatString:formatString zone:zone];
return obj;
}
- free
{
[formatString free];
return [super free];
}
- (BOOL)isEuropeanStyle
{
return isEuropeanStyle;
}
- setEuropeanStyle:(BOOL)flag
{
if (flag != isEuropeanStyle) {
isEuropeanStyle = flag;
if (isEuropeanStyle) {
[self setRegexStr:EU_DATE_REGEX_1];
[self addRegexStr:EU_DATE_REGEX_2];
[self addRegexStr:EU_DATE_REGEX_3];
} else {
[self setRegexStr:AM_DATE_REGEX_1];
[self addRegexStr:AM_DATE_REGEX_2];
[self addRegexStr:AM_DATE_REGEX_3];
}
}
// reset the pieces and stuff.
[self isEntryAcceptable:[self stringValue]];
return self;
}
- (BOOL)doesFormatDates
{
return doesFormat;
}
- setFormatDates:(BOOL)flag
{
doesFormat = flag;
[self formatDate];
return self;
}
- (const char *)formatString
{
return [formatString stringValue];
}
- setFormatString:(const char *)str
{
[formatString setStringValue:str];
[self formatDate];
return self;
}
- (BOOL)MO_finishCheckingDate
// Make sure that the days in the month is valid. We can assume that if we're
// here, the regular expression parts have checked out.
{
int monthNum= [self month], yearNum= [self year], dayNum= [self day];
if ((monthNum<0) || (yearNum<0) || (dayNum<0)) {
return NO;
}
switch (monthNum) {
// "thirty days has ..."
case 9: // "September, ..."
case 4: // "April, ..."
case 6: // "June, and ..."
case 11: // "November ..."
if (dayNum>30) return NO;
break;
// "All the rest have thirty one ..."
// (but the regular expression made sure it was at least 31 or
// less.)
// "Except for February which has 28 ..."
// "(or 29 if its a leap year)."
case 2: // February
if (dayNum > 29) return NO;
if (dayNum == 29) {
BOOL isLeapYear=NO;
// figure out if it's a leap year.
if (yearNum%4 == 0) {
if (yearNum%100 == 0) {
if (yearNum%1000 == 0) {
isLeapYear=YES;
}
} else {
isLeapYear=YES;
}
}
if (!isLeapYear) return NO;
}
break;
default:
break;
}
return YES;
}
- (BOOL)isEntryAcceptable:(const char *)aString
{
if ([super isEntryAcceptable:aString]) {
if ((aString) && (*aString)) {
return [self MO_finishCheckingDate];
} else {
return YES;
}
} else {
return NO;
}
}
- (int)day
{
const char *day;
int dayNum = 0;
if (isEuropeanStyle) {
day = [self pieceAt:2];
} else {
day = [self pieceAt:3];
}
if (day) {
dayNum = atoi(day);
} else {
dayNum = -1;
}
return dayNum;
}
- (int)month
{
const char *month;
int monthNum = 0;
if (isEuropeanStyle) {
month = [self pieceAt:3];
} else {
month = [self pieceAt:2];
}
if (!month) {
return -1;
}
// Is month numeric or name?
if (NXIsDigit(*month)) {
// it's number
monthNum = atoi(month);
} else {
// it's name
if (NXOrderStrings(month, "ja", NO, 2, NULL) == 0) {
monthNum = 1;
} else if (NXOrderStrings(month, "f", NO, 1, NULL) == 0) {
monthNum = 2;
} else if (NXOrderStrings(month, "mar", NO, 3, NULL) == 0) {
monthNum = 3;
} else if (NXOrderStrings(month, "ap", NO, 2, NULL) == 0) {
monthNum = 4;
} else if (NXOrderStrings(month, "may", NO, 3, NULL) == 0) {
monthNum = 5;
} else if (NXOrderStrings(month, "jun", NO, 3, NULL) == 0) {
monthNum = 6;
} else if (NXOrderStrings(month, "jul", NO, 3, NULL) == 0) {
monthNum = 7;
} else if (NXOrderStrings(month, "au", NO, 2, NULL) == 0) {
monthNum = 8;
} else if (NXOrderStrings(month, "s", NO, 1, NULL) == 0) {
monthNum = 9;
} else if (NXOrderStrings(month, "o", NO, 1, NULL) == 0) {
monthNum = 10;
} else if (NXOrderStrings(month, "n", NO, 1, NULL) == 0) {
monthNum = 11;
} else if (NXOrderStrings(month, "d", NO, 1, NULL) == 0) {
monthNum = 12;
} else {
// this shouldn't happen since the regular expression
// has been checked to make sure it was a valid month.
return -1;
}
}
return monthNum;
}
- (int)year
{
const char *year;
int yearNum = 0;
if (isEuropeanStyle) {
year = [self pieceAt:4];
} else {
year = [self pieceAt:4];
}
if (!year) {
return -1;
}
yearNum=atoi(year);
if (yearNum < 100) {
// adjust for current century
struct timeval tv;
struct timezone tz;
struct tm *tmp = NULL;
int currentCentury;
gettimeofday(&tv, &tz);
tmp = localtime(&tv.tv_sec);
currentCentury = 1900 + (tmp->tm_year / 100);
yearNum += currentCentury;
}
return yearNum;
}
- endEditing:textObject
{
[super endEditing:textObject];
[self formatDate];
return self;
}
- (const char *)MO_monthNameForNum:(int)monthNum short:(BOOL)flag
{
static char buf[20];
switch(monthNum) {
case 1:
if (flag) {
strcpy(buf, "Jan");
} else {
strcpy(buf, "January");
}
break;
case 2:
if (flag) {
strcpy(buf, "Feb");
} else {
strcpy(buf, "February");
}
break;
case 3:
if (flag) {
strcpy(buf, "Mar");
} else {
strcpy(buf, "March");
}
break;
case 4:
if (flag) {
strcpy(buf, "Apr");
} else {
strcpy(buf, "April");
}
break;
case 5:
strcpy(buf, "May");
break;
case 6:
if (flag) {
strcpy(buf, "Jun");
} else {
strcpy(buf, "June");
}
break;
case 7:
if (flag) {
strcpy(buf, "Jul");
} else {
strcpy(buf, "July");
}
break;
case 8:
if (flag) {
strcpy(buf, "Aug");
} else {
strcpy(buf, "August");
}
break;
case 9:
if (flag) {
strcpy(buf, "Sep");
} else {
strcpy(buf, "September");
}
break;
case 10:
if (flag) {
strcpy(buf, "Oct");
} else {
strcpy(buf, "October");
}
break;
case 11:
if (flag) {
strcpy(buf, "Nov");
} else {
strcpy(buf, "November");
}
break;
case 12:
if (flag) {
strcpy(buf, "Dec");
} else {
strcpy(buf, "December");
}
break;
default:
strcpy(buf, "");;
}
return buf;
}
- formatDate
{
static char buf[1024];
if (doesFormat) {
const char *format = [formatString stringValue];
const char *fPtr = NULL;
char *bPtr = NULL;
const char *month = NULL;
int dayNum = [self day], monthNum = [self month];
int yearNum = [self year];
if ((!format) || (!*format) || (dayNum < 0) ||
(monthNum < 0) || (yearNum < 0)) {
return nil;
}
bPtr = buf;
fPtr = format;
while (*fPtr) {
if (*fPtr == '%') {
// Its a format code...
fPtr++;
switch (*fPtr) {
case 'd':
sprintf(bPtr, "%d", dayNum);
break;
case 'D':
sprintf(bPtr, "%02d", dayNum);
break;
case 'm':
sprintf(bPtr, "%d", monthNum);
break;
case 'M':
sprintf(bPtr, "%02d", monthNum);
break;
case 'n':
month = [self MO_monthNameForNum:monthNum short:YES];
sprintf(bPtr, "%s", month);
break;
case 'N':
month = [self MO_monthNameForNum:monthNum short:NO];
sprintf(bPtr, "%s", month);
break;
case 'y':
sprintf(bPtr, "%d", yearNum%100);
break;
case 'Y':
sprintf(bPtr, "%d", yearNum);
break;
case '%':
strcpy(bPtr, "%");
break;
default:
NXLogError("%s: Invalid date "
"formatting code.", [[self class] name]);
break;
}
bPtr = buf + strlen(buf);
fPtr++;
} else {
// Just copy the character...
*bPtr = *fPtr;
bPtr++; fPtr++;
}
}
*bPtr = '\0';
// call super to avoid an infinite loop...
// (our setStringValue calls formatDate)
[super setStringValue:buf];
}
return self;
}
- setDoubleValue:(double)aDouble
{
id ret = [super setDoubleValue:aDouble];
[self formatDate];
return ret;
}
- setFloatValue:(float)aFloat
{
id ret = [super setFloatValue:aFloat];
[self formatDate];
return ret;
}
- setIntValue:(int)anInt
{
id ret = [super setIntValue:anInt];
[self formatDate];
return ret;
}
- setStringValue:(const char *)aString
{
id ret = [super setStringValue:aString];
[self formatDate];
return ret;
}
- setStringValueNoCopy:(const char *)aString
{
id ret = [super setStringValueNoCopy:aString];
[self formatDate];
return ret;
}
- setStringValueNoCopy:(char *)aString shouldFree:(BOOL)flag
{
id ret = [super setStringValueNoCopy:aString shouldFree:flag];
[self formatDate];
return ret;
}
- read:(NXTypedStream *)strm
{
int classVersion;
[super read:strm];
classVersion = NXTypedStreamClassVersion(strm, CLASS_NAME);
switch (classVersion) {
case 0: // First version.
NXReadType(strm, "C", &isEuropeanStyle);
NXReadType(strm, "C", &doesFormat);
formatString = NXReadObject(strm);
break;
default:
NXLogError("[%s read:] class version %d cannot read "
"instances archived with version %d",
CLASS_NAME, CLASS_VERSION, classVersion);
isEuropeanStyle = NO;
doesFormat = NO;
formatString = [[MOStringClass allocFromZone:[self zone]] init];
break;
}
return self;
}
- write:(NXTypedStream *)strm;
{
[super write:strm];
NXWriteType(strm, "C", &isEuropeanStyle);
NXWriteType(strm, "C", &doesFormat);
NXWriteObject(strm, formatString);
return self;
}
- (const char *)getInspectorClassName
{
return "MODateTextCellInspector";
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.