Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions Source/NSDatePickerCell.m
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
#import "AppKit/NSDatePickerCell.h"
#import "AppKit/NSColor.h"

@interface NSDatePickerCell (Private)
- (void) _updateDateFormat;
@end

@implementation NSDatePickerCell

- (void) dealloc
Expand All @@ -60,11 +64,119 @@ - (id) initTextCell: (NSString*)aString
[self setBezeled: YES];
_datePickerElements = NSYearMonthDayDatePickerElementFlag
| NSHourMinuteSecondDatePickerElementFlag;
[self _updateDateFormat];
}

return self;
}

/* The template holds one letter per element the picker shows. The pattern
generator turns it into the pattern the locale writes those elements in.
'j' stands for the hour in the clock the locale uses, twelve or twenty
four.
*/
- (NSString *) _dateFormatTemplate
{
NSMutableString *template = [NSMutableString stringWithCapacity: 8];
NSDatePickerElementFlags elements = _datePickerElements;

if (elements & NSEraDatePickerElementFlag)
{
[template appendString: @"G"];
}
if (elements & NSYearMonthDatePickerElementFlag)
{
[template appendString: @"yM"];
}
if ((elements & NSYearMonthDayDatePickerElementFlag)
== NSYearMonthDayDatePickerElementFlag)
{
[template appendString: @"d"];
}
if (elements & NSHourMinuteDatePickerElementFlag)
{
[template appendString: @"jm"];
}
if ((elements & NSHourMinuteSecondDatePickerElementFlag)
== NSHourMinuteSecondDatePickerElementFlag)
{
[template appendString: @"s"];
}
if (elements & NSTimeZoneDatePickerElementFlag)
{
[template appendString: @"z"];
}

return template;
}

/* Used when the pattern generator is unavailable, which is the case in a
build without ICU.
*/
- (NSString *) _fallbackDateFormat
{
NSMutableString *format = [NSMutableString stringWithCapacity: 24];
NSDatePickerElementFlags elements = _datePickerElements;

if (elements & NSYearMonthDatePickerElementFlag)
{
[format appendString: @"y-MM"];
if ((elements & NSYearMonthDayDatePickerElementFlag)
== NSYearMonthDayDatePickerElementFlag)
{
[format appendString: @"-dd"];
}
}
if (elements & NSHourMinuteDatePickerElementFlag)
{
if ([format length] > 0)
{
[format appendString: @" "];
}
[format appendString: @"HH:mm"];
if ((elements & NSHourMinuteSecondDatePickerElementFlag)
== NSHourMinuteSecondDatePickerElementFlag)
{
[format appendString: @":ss"];
}
}
if (elements & NSTimeZoneDatePickerElementFlag)
{
[format appendString: @" zzz"];
}
if (elements & NSEraDatePickerElementFlag)
{
[format appendString: @" G"];
}

return format;
}

- (void) _updateDateFormat
{
NSDateFormatter *formatter = (NSDateFormatter *)[self formatter];
NSString *template;
NSString *format = nil;

if (![formatter isKindOfClass: [NSDateFormatter class]])
{
return;
}

template = [self _dateFormatTemplate];
if ([template length] > 0)
{
format = [NSDateFormatter dateFormatFromTemplate: template
options: 0
locale: [formatter locale]];
if (format == nil)
{
format = [self _fallbackDateFormat];
}
}
[formatter setDateFormat: (format == nil) ? (NSString *)@"" : format];
}

- (NSColor *) backgroundColor
{
return _backgroundColor;
Expand Down Expand Up @@ -93,6 +205,7 @@ - (NSDatePickerElementFlags) datePickerElements
- (void) setDatePickerElements: (NSDatePickerElementFlags)flags
{
_datePickerElements = flags;
[self _updateDateFormat];
}

- (NSDatePickerMode) datePickerMode
Expand Down Expand Up @@ -142,6 +255,25 @@ - (void) setDateValue: (NSDate *)date
[self setObjectValue: [self _clampedDate: date]];
}

/* NSCell keeps the text its formatter made when the value was set, which is
stale once the elements or the locale change, and -stringForObjectValue:
builds that text from a %-style format rather than from the pattern.
*/
- (NSString *) stringValue
{
NSDateFormatter *formatter = (NSDateFormatter *)[self formatter];
id value = [self objectValue];

if ([value isKindOfClass: [NSDate class]]
&& [formatter isKindOfClass: [NSDateFormatter class]]
&& [[formatter dateFormat] length] > 0)
{
return [formatter stringFromDate: (NSDate *)value];
}

return [super stringValue];
}

- (id) delegate
{
return _delegate;
Expand Down Expand Up @@ -170,6 +302,7 @@ - (NSLocale *) locale
- (void) setLocale: (NSLocale *)locale
{
[[self formatter] setLocale: locale];
[self _updateDateFormat];
}

- (NSDate *) maxDate
Expand Down Expand Up @@ -242,6 +375,13 @@ - (id) initWithCoder: (NSCoder *)aDecoder
{
if ((self = [super initWithCoder: aDecoder]))
{
if (![[self formatter] isKindOfClass: [NSDateFormatter class]])
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];

[self setFormatter: formatter];
RELEASE(formatter);
}
if ([aDecoder allowsKeyedCoding])
{
[self setTimeInterval: [aDecoder decodeDoubleForKey: @"NSTimeInterval"]];
Expand Down
123 changes: 123 additions & 0 deletions Tests/gui/NSDatePicker/display.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/* The text a date picker shows follows the elements it is configured with,
in the order and the form its locale writes them, and in its own time zone.
The assertions look for the parts of the date rather than a whole string,
so that they hold for the pattern any CLDR version produces. A build
without ICU has no pattern generator and falls back to a fixed order, so
the one assertion that compares two locales is made only when the
generator answers. The picker uses the theme and font backend, so the set
is skipped when the backend is unavailable.
*/
#include "Testing.h"

#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSDate.h>
#include <Foundation/NSDateFormatter.h>
#include <Foundation/NSGeometry.h>
#include <Foundation/NSLocale.h>
#include <Foundation/NSString.h>
#include <Foundation/NSTimeZone.h>

#include <AppKit/NSApplication.h>
#include <AppKit/NSDatePicker.h>
#include <AppKit/NSDatePickerCell.h>

static BOOL
has(NSString *string, NSString *part)
{
return [string rangeOfString: part].length > 0;
}

int
main(int argc, char **argv)
{
NSDatePicker *dp;
NSString *text;
NSString *american;
NSDate *date;
BOOL generator;

START_SET("NSDatePicker display")

NS_DURING
{
[NSApplication sharedApplication];
}
NS_HANDLER
{
if ([[localException name] isEqualToString: NSInternalInconsistencyException])
SKIP("It looks like GNUstep backend is not yet installed")
}
NS_ENDHANDLER

NS_DURING
{
generator = ([NSDateFormatter dateFormatFromTemplate: @"yMd"
options: 0
locale: [NSLocale localeWithLocaleIdentifier: @"en_US"]]
!= nil);

/* 8 March 2023, 20:26:40 GMT. */
date = [NSDate dateWithTimeIntervalSinceReferenceDate: 700000000.0];
dp = AUTORELEASE([[NSDatePicker alloc]
initWithFrame: NSMakeRect(0, 0, 260, 26)]);
[dp setLocale: [NSLocale localeWithLocaleIdentifier: @"en_US"]];
[dp setTimeZone: [NSTimeZone timeZoneWithName: @"GMT"]];
[dp setDateValue: date];

text = [[dp cell] stringValue];
PASS(has(text, @"2023") && has(text, @":26:") && has(text, @"40"),
"the default elements show the date and the time to the second");
PASS(!has(text, @"+0000") && ![text isEqualToString: [date description]],
"the shown text is not the description of the date");

[dp setDatePickerElements: NSYearMonthDayDatePickerElementFlag];
text = [[dp cell] stringValue];
PASS(has(text, @"2023") && !has(text, @":26"),
"the year-month-day elements drop the time");

[dp setDatePickerElements: NSHourMinuteSecondDatePickerElementFlag];
text = [[dp cell] stringValue];
PASS(has(text, @":26:") && has(text, @"40") && !has(text, @"2023"),
"the hour-minute-second elements drop the date");

[dp setDatePickerElements: NSHourMinuteDatePickerElementFlag];
text = [[dp cell] stringValue];
PASS(has(text, @":26") && !has(text, @":26:"),
"the hour-minute elements drop the seconds");

[dp setDatePickerElements: NSYearMonthDatePickerElementFlag];
text = [[dp cell] stringValue];
PASS(has(text, @"2023") && !has(text, @":"),
"the year-month elements drop the day and the time");

/* The time zone the picker is set to decides the hour it shows. */
[dp setDatePickerElements: NSHourMinuteSecondDatePickerElementFlag];
text = [[dp cell] stringValue];
[dp setTimeZone: [NSTimeZone timeZoneForSecondsFromGMT: 3600 * 5]];
PASS(![[[dp cell] stringValue] isEqualToString: text],
"the shown time follows the time zone of the picker");

[dp setTimeZone: [NSTimeZone timeZoneWithName: @"GMT"]];
[dp setDatePickerElements: NSYearMonthDayDatePickerElementFlag];
american = [[dp cell] stringValue];
[dp setLocale: [NSLocale localeWithLocaleIdentifier: @"de_DE"]];
if (generator)
{
PASS(![[[dp cell] stringValue] isEqualToString: american],
"the shown date follows the locale of the picker");
}
}
NS_HANDLER
{
if ([[localException name] isEqualToString: NSInternalInconsistencyException]
|| [[localException name] isEqualToString: @"NSWindowServerCommunicationException"])
SKIP("No display available")
else
[localException raise];
}
NS_ENDHANDLER

END_SET("NSDatePicker display")

return 0;
}
Loading