@@ -26,27 +26,57 @@ const currentLocale = detectLocale();
2626
2727const DEFAULT_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' ;
2828const DEFAULT_DATE_FORMAT = '%Y-%m-%d' ;
29- const DEFAULT_DATE_MONTH_FORMAT = '%Y-%m' ;
29+ const DEFAULT_DATE_WEEK_FORMAT = '%Y-%m-%d W%V' ;
30+ const DEFAULT_DATE_MONTH_FORMAT = '%Y %b' ;
3031const DEFAULT_DATE_QUARTER_FORMAT = '%Y-Q%q' ;
3132const DEFAULT_DATE_YEAR_FORMAT = '%Y' ;
3233
33- function getTimeFormatByGrain ( grain : string | undefined ) : string {
34- switch ( grain ) {
35- case 'day' :
36- case 'week' :
37- return DEFAULT_DATE_FORMAT ;
38- case 'month' :
39- return DEFAULT_DATE_MONTH_FORMAT ;
40- case 'quarter' :
41- return DEFAULT_DATE_QUARTER_FORMAT ;
42- case 'year' :
43- return DEFAULT_DATE_YEAR_FORMAT ;
44- case 'second' :
45- case 'minute' :
46- case 'hour' :
47- default :
48- return DEFAULT_DATETIME_FORMAT ;
34+ function getFormatByGrain ( grain ?: string ) : string {
35+ // Grains that should show date and time (sub-day granularities)
36+ const dateTimeGrains = [ 'second' , 'minute' , 'hour' ] ;
37+
38+ // Grains that should show date only (day and above granularities)
39+ const dateOnlyGrains = [ 'day' , 'week' , 'month' , 'quarter' , 'year' ] ;
40+
41+ if ( grain === 'day' ) {
42+ return DEFAULT_DATE_FORMAT ;
43+ }
44+
45+ if ( grain === 'week' ) {
46+ return DEFAULT_DATE_WEEK_FORMAT ;
47+ }
48+
49+ if ( grain === 'month' ) {
50+ return DEFAULT_DATE_MONTH_FORMAT ;
51+ }
52+
53+ if ( grain === 'quarter' ) {
54+ return DEFAULT_DATE_QUARTER_FORMAT ;
55+ }
56+
57+ if ( grain === 'year' ) {
58+ return DEFAULT_DATE_YEAR_FORMAT ;
59+ }
60+
61+ if ( ! grain || dateTimeGrains . includes ( grain ) ) {
62+ return DEFAULT_DATETIME_FORMAT ;
63+ }
64+
65+ if ( dateOnlyGrains . includes ( grain ) ) {
66+ return DEFAULT_DATE_FORMAT ;
4967 }
68+
69+ // Fallback to datetime for unknown grains
70+ return DEFAULT_DATETIME_FORMAT ;
71+ }
72+
73+ export function formatDateByGranularity ( value : Date | string | number , granularity ?: string ) : string {
74+ const date = value instanceof Date ? value : new Date ( value ) ;
75+ if ( Number . isNaN ( date . getTime ( ) ) ) {
76+ return 'Invalid date' ;
77+ }
78+
79+ return timeFormat ( getFormatByGrain ( granularity ) ) ( date ) ;
5080}
5181
5282function parseNumber ( value : any ) : number {
@@ -73,78 +103,125 @@ export type FormatValueOptions = FormatValueMember & {
73103 emptyPlaceholder ?: string ;
74104} ;
75105
76- export function formatValue (
77- value : any ,
78- { type, format, currency = 'USD' , granularity, locale = currentLocale , emptyPlaceholder = '∅' } : FormatValueOptions
79- ) : string {
80- if ( value === null || value === undefined ) {
81- return emptyPlaceholder ;
106+ export type GetFormatOptions = {
107+ locale ?: string ;
108+ } ;
109+
110+ export type GetFormatResult = {
111+ formatString : string | null ;
112+ formatFunc : ( value : any ) => string ;
113+ } ;
114+
115+ function formatBoolean ( value : any ) : string {
116+ if ( typeof value === 'boolean' ) {
117+ return value . toString ( ) ;
82118 }
83119
84- if ( type === 'boolean' ) {
85- if ( typeof value === 'boolean' ) {
86- return value . toString ( ) ;
87- }
120+ if ( typeof value === 'number' ) {
121+ return Boolean ( value ) . toString ( ) ;
122+ }
88123
89- if ( typeof value === 'number' ) {
90- return Boolean ( value ) . toString ( ) ;
91- }
124+ // Some SQL drivers return booleans as '0'/'1' or 'true'/'false' strings, It's incorrect behaivour in Cube,
125+ // but let's format it as boolean for backward compatibility.
126+ if ( value === '0' || value === 'false' ) {
127+ return 'false' ;
128+ }
92129
93- // Some SQL drivers return booleans as '0'/'1' or 'true'/'false' strings, It's incorrect behaivour in Cube,
94- // but let's format it as boolean for backward compatibility.
95- if ( value === '0' || value === 'false' ) {
96- return 'false' ;
97- }
130+ if ( value === '1' || value === 'true' ) {
131+ return 'true' ;
132+ }
98133
99- if ( value === '1' || value === 'true' ) {
100- return 'true' ;
101- }
134+ return String ( value ) ;
135+ }
136+
137+ export function getFormat (
138+ member : FormatValueMember ,
139+ { locale = currentLocale } : GetFormatOptions = { }
140+ ) : GetFormatResult {
141+ const { type, format, currency = 'USD' , granularity } = member ;
102142
103- return String ( value ) ;
143+ if ( type === 'boolean' ) {
144+ return { formatString : null , formatFunc : formatBoolean } ;
104145 }
105146
106147 if ( format && typeof format === 'object' ) {
107148 if ( format . type === 'custom-numeric' ) {
108- return d3Format ( format . value ) ( parseNumber ( value ) ) ;
149+ return {
150+ formatString : format . value ,
151+ formatFunc : ( value ) => d3Format ( format . value ) ( parseNumber ( value ) ) ,
152+ } ;
109153 }
110154
111155 if ( format . type === 'custom-time' ) {
112- const date = new Date ( value ) ;
113- return Number . isNaN ( date . getTime ( ) ) ? 'Invalid date' : timeFormat ( format . value ) ( date ) ;
156+ return {
157+ formatString : format . value ,
158+ formatFunc : ( value ) => {
159+ const date = new Date ( value ) ;
160+ return Number . isNaN ( date . getTime ( ) ) ? 'Invalid date' : timeFormat ( format . value ) ( date ) ;
161+ } ,
162+ } ;
114163 }
115164
116165 // { type: 'link', label: string } — return value as string
117- return String ( value ) ;
166+ return { formatString : null , formatFunc : ( value ) => String ( value ) } ;
118167 }
119168
120169 if ( typeof format === 'string' ) {
121170 switch ( format ) {
122171 case 'currency' :
123- return getD3NumericLocale ( locale , currency ) . format ( DEFAULT_CURRENCY_FORMAT ) ( parseNumber ( value ) ) ;
172+ return {
173+ formatString : DEFAULT_CURRENCY_FORMAT ,
174+ formatFunc : ( value ) => getD3NumericLocale ( locale , currency ) . format ( DEFAULT_CURRENCY_FORMAT ) ( parseNumber ( value ) ) ,
175+ } ;
124176 case 'percent' :
125- return getD3NumericLocale ( locale ) . format ( DEFAULT_PERCENT_FORMAT ) ( parseNumber ( value ) ) ;
177+ return {
178+ formatString : DEFAULT_PERCENT_FORMAT ,
179+ formatFunc : ( value ) => getD3NumericLocale ( locale ) . format ( DEFAULT_PERCENT_FORMAT ) ( parseNumber ( value ) ) ,
180+ } ;
126181 case 'number' :
127- return getD3NumericLocale ( locale ) . format ( DEFAULT_NUMBER_FORMAT ) ( parseNumber ( value ) ) ;
182+ return {
183+ formatString : DEFAULT_NUMBER_FORMAT ,
184+ formatFunc : ( value ) => getD3NumericLocale ( locale ) . format ( DEFAULT_NUMBER_FORMAT ) ( parseNumber ( value ) ) ,
185+ } ;
128186 case 'id' :
129- return d3Format ( DEFAULT_ID_FORMAT ) ( parseNumber ( value ) ) ;
187+ return {
188+ formatString : DEFAULT_ID_FORMAT ,
189+ formatFunc : ( value ) => d3Format ( DEFAULT_ID_FORMAT ) ( parseNumber ( value ) ) ,
190+ } ;
130191 case 'imageUrl' :
131192 case 'link' :
132193 default :
133- return String ( value ) ;
194+ return { formatString : null , formatFunc : ( value ) => String ( value ) } ;
134195 }
135196 }
136197
137198 // No explicit format — infer from type
138199 if ( type === 'time' ) {
139- const date = new Date ( value ) ;
140- if ( Number . isNaN ( date . getTime ( ) ) ) return 'Invalid date' ;
141-
142- return timeFormat ( getTimeFormatByGrain ( granularity ) ) ( date ) ;
200+ return {
201+ formatString : getFormatByGrain ( granularity ) ,
202+ formatFunc : ( value ) => formatDateByGranularity ( value , granularity ) ,
203+ } ;
143204 }
144205
145206 if ( type === 'number' ) {
146- return getD3NumericLocale ( locale , currency ) . format ( DEFAULT_NUMBER_FORMAT ) ( parseNumber ( value ) ) ;
207+ return {
208+ formatString : DEFAULT_NUMBER_FORMAT ,
209+ formatFunc : ( value ) => getD3NumericLocale ( locale , currency ) . format ( DEFAULT_NUMBER_FORMAT ) ( parseNumber ( value ) ) ,
210+ } ;
147211 }
148212
149- return String ( value ) ;
213+ return { formatString : null , formatFunc : ( value ) => String ( value ) } ;
214+ }
215+
216+ export function formatValue (
217+ value : any ,
218+ options : FormatValueOptions
219+ ) : string {
220+ const { emptyPlaceholder = '∅' } = options ;
221+
222+ if ( value === null || value === undefined ) {
223+ return emptyPlaceholder ;
224+ }
225+
226+ return getFormat ( options , { locale : options . locale } ) . formatFunc ( value ) ;
150227}
0 commit comments