001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.commons.i18n;
020
021import java.nio.charset.Charset;
022import java.text.DateFormat;
023import java.text.SimpleDateFormat;
024import java.util.ArrayList;
025import java.util.List;
026import java.util.Locale;
027import java.util.Map;
028import java.util.TimeZone;
029
030import lucee.commons.date.TimeZoneConstants;
031import lucee.commons.io.IOUtil;
032import lucee.commons.io.res.Resource;
033import lucee.commons.lang.ExceptionUtil;
034import lucee.commons.lang.StringUtil;
035import lucee.runtime.config.Config;
036import lucee.runtime.engine.ThreadLocalPageContext;
037
038import org.apache.commons.collections.map.ReferenceMap;
039
040public class FormatUtil {
041
042        public static final short FORMAT_TYPE_DATE=1;
043        public static final short FORMAT_TYPE_TIME=2;
044        public static final short FORMAT_TYPE_DATE_TIME=3;
045        public static final short FORMAT_TYPE_DATE_ALL=4;
046 
047        private final static Map<String,DateFormat[]> formats=new ReferenceMap(ReferenceMap.SOFT,ReferenceMap.SOFT);
048        
049        public static DateFormat[] getDateTimeFormats(Locale locale,TimeZone tz,boolean lenient) {
050
051                String id="dt-"+locale.hashCode()+"-"+tz.getID()+"-"+lenient;
052                DateFormat[] df=formats.get(id);
053                if(df==null) {
054                        List<DateFormat> list=new ArrayList<DateFormat>();
055                        list.add(DateFormat.getDateTimeInstance(DateFormat.FULL,DateFormat.FULL,locale));
056                        list.add(DateFormat.getDateTimeInstance(DateFormat.FULL,DateFormat.LONG,locale));
057                        list.add(DateFormat.getDateTimeInstance(DateFormat.FULL,DateFormat.MEDIUM,locale));
058                        list.add(DateFormat.getDateTimeInstance(DateFormat.FULL,DateFormat.SHORT,locale));
059
060                        list.add(DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.FULL,locale));
061                        list.add(DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG,locale));
062                        list.add(DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.MEDIUM,locale));
063                        list.add(DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.SHORT,locale));
064
065                        list.add(DateFormat.getDateTimeInstance(DateFormat.MEDIUM,DateFormat.FULL,locale));
066                        list.add(DateFormat.getDateTimeInstance(DateFormat.MEDIUM,DateFormat.LONG,locale));
067                        list.add(DateFormat.getDateTimeInstance(DateFormat.MEDIUM,DateFormat.MEDIUM,locale));
068                        list.add(DateFormat.getDateTimeInstance(DateFormat.MEDIUM,DateFormat.SHORT,locale));
069
070                        list.add(DateFormat.getDateTimeInstance(DateFormat.SHORT,DateFormat.FULL,locale));
071                        list.add(DateFormat.getDateTimeInstance(DateFormat.SHORT,DateFormat.LONG,locale));
072                        list.add(DateFormat.getDateTimeInstance(DateFormat.SHORT,DateFormat.MEDIUM,locale));
073                        list.add(DateFormat.getDateTimeInstance(DateFormat.SHORT,DateFormat.SHORT,locale));
074                        add24(list, locale);
075                        addCustom(list, locale, FORMAT_TYPE_DATE_TIME);
076                        df=list.toArray(new DateFormat[list.size()]);
077                        
078                        for(int i=0;i<df.length;i++){
079                                df[i].setLenient(lenient);
080                                df[i].setTimeZone(tz);
081                        }
082                        
083                        formats.put(id, df);
084                }
085                
086                return df;
087        }
088
089        
090        public static DateFormat[] getDateFormats(Locale locale,TimeZone tz,boolean lenient) {
091                String id="d-"+locale.hashCode()+"-"+tz.getID()+"-"+lenient;
092                DateFormat[] df= formats.get(id);
093                if(df==null) {
094                        List<DateFormat> list=new ArrayList<DateFormat>();
095                        list.add(DateFormat.getDateInstance(DateFormat.FULL,locale));
096                        list.add(DateFormat.getDateInstance(DateFormat.LONG,locale));
097                        list.add(DateFormat.getDateInstance(DateFormat.MEDIUM,locale));
098                        list.add(DateFormat.getDateInstance(DateFormat.SHORT,locale));
099                        addCustom(list, locale, FORMAT_TYPE_DATE);
100                        df=list.toArray(new DateFormat[list.size()]);
101                        
102                        for(int i=0;i<df.length;i++){
103                                df[i].setLenient(lenient);
104                                df[i].setTimeZone(tz);
105                        }
106                        formats.put(id, df);
107                }
108                return df;
109        }
110        
111        public static DateFormat[] getTimeFormats(Locale locale,TimeZone tz,boolean lenient) {
112                String id="t-"+locale.hashCode()+"-"+tz.getID()+"-"+lenient;
113                DateFormat[] df= formats.get(id);
114                if(df==null) {
115                        List<DateFormat> list=new ArrayList<DateFormat>();
116                        list.add(DateFormat.getTimeInstance(DateFormat.FULL,locale));
117                        list.add(DateFormat.getTimeInstance(DateFormat.LONG,locale));
118                        list.add(DateFormat.getTimeInstance(DateFormat.MEDIUM,locale));
119                        list.add(DateFormat.getTimeInstance(DateFormat.SHORT,locale));
120                        add24(list, locale);
121                        addCustom(list, locale, FORMAT_TYPE_TIME);
122                        df=list.toArray(new DateFormat[list.size()]);
123                        
124                        for(int i=0;i<df.length;i++){
125                                 df[i].setLenient(lenient);
126                                 df[i].setTimeZone(tz);
127                        }
128                        formats.put(id, df);
129                }
130                return df;
131        }
132        
133
134        private static void add24(List<DateFormat> list,Locale locale) {
135                
136                // if found h:mm:ss a add H:mm:ss ...
137                String p;
138                int index;
139                SimpleDateFormat sdf;
140                DateFormat[] df=list.toArray(new DateFormat[list.size()]);
141                for(int i=0;i<df.length;i++){
142                        if(df[i] instanceof SimpleDateFormat) {
143                                p=((SimpleDateFormat) df[i]).toPattern()+"";
144                                
145                                if(check(list,p,locale,"hh:mm:ss a","HH:mm:ss")) continue;
146                                if(check(list,p,locale,"h:mm:ss a","H:mm:ss")) continue;
147                                if(check(list,p,locale,"hh:mm a","HH:mm")) continue;
148                                if(check(list,p,locale,"h:mm a","H:mm")) continue;
149                                
150                                if(check(list,p,locale,"hh:mm:ssa","HH:mm:ss")) continue;
151                                if(check(list,p,locale,"h:mm:ssa","H:mm:ss")) continue;
152                                if(check(list,p,locale,"hh:mma","HH:mm")) continue;
153                                if(check(list,p,locale,"h:mma","H:mm")) continue;
154                                
155                                //if(check(list,p,locale,"HH:mm:ss","hh:mm:ss a")) continue;
156                                //if(check(list,p,locale,"H:mm:ss","h:mm:ss a")) continue;
157                                //if(check(list,p,locale,"HH:mm","hh:mm a")) continue;
158                                //if(check(list,p,locale,"H:mm","h:mm a")) continue;
159                        }
160                }
161        }
162        
163        private static boolean check(List<DateFormat> list, String p,Locale locale, String from, String to) {
164                int index = p.indexOf(from);
165                if(index!=-1) {
166                        p=StringUtil.replace(p, from, to, true);
167                        SimpleDateFormat sdf = new SimpleDateFormat(p,locale);
168                        if(!list.contains(sdf))list.add(sdf);
169                        return true;
170                }
171                return false;
172        }
173
174
175        private static void addCustom(List<DateFormat> list,Locale locale,short formatType) {
176                // get custom formats from file
177                Config config = ThreadLocalPageContext.getConfig();
178                Resource dir=config.getConfigDir().getRealResource("locales");
179                if(dir.isDirectory()) {
180                        String appendix="-datetime";
181                        if(formatType==FORMAT_TYPE_DATE)appendix="-date";
182                        if(formatType==FORMAT_TYPE_TIME)appendix="-time";
183                        
184                        Resource file = dir.getRealResource(locale.getLanguage()+"-"+locale.getCountry()+appendix+".df");
185                        if(file.isFile()) {
186                                try {
187                                        String content=IOUtil.toString(file, (Charset)null);
188                                        String[] arr = lucee.runtime.type.util.ListUtil.listToStringArray(content, '\n');
189                                        String line;
190                                        SimpleDateFormat sdf;
191                                        for(int i=0;i<arr.length;i++){
192                                                line=arr[i].trim();
193                                                if(StringUtil.isEmpty(line)) continue;
194                                                sdf = new SimpleDateFormat(line,locale);
195                                                if(!list.contains(sdf))list.add(sdf);
196                                        }
197                                        
198                                } 
199                                catch (Throwable t) {
200                        ExceptionUtil.rethrowIfNecessary(t);
201                    }
202                        }
203                }
204        }
205        
206        /**
207         * CFML Supported LS Formats
208         * @param locale
209         * @param tz
210         * @param lenient
211         * @return
212         */
213        public static DateFormat[] getCFMLFormats(TimeZone tz,boolean lenient) {
214                String id="cfml-"+Locale.ENGLISH.hashCode()+"-"+tz.getID()+"-"+lenient;
215                DateFormat[] df= formats.get(id);
216                if(df==null) {
217                        df= new SimpleDateFormat[]{
218                                          new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy",Locale.ENGLISH)
219                                         ,new SimpleDateFormat("MMMM dd, yyyy HH:mm:ss a zzz",Locale.ENGLISH)
220                                         ,new SimpleDateFormat("MMM dd, yyyy HH:mm:ss a",Locale.ENGLISH)         
221                                         ,new SimpleDateFormat("MMM dd, yyyy HH:mm:ss",Locale.ENGLISH)   
222                                         ,new SimpleDateFormat("MMMM d yyyy HH:mm:ssZ",Locale.ENGLISH)
223                                         ,new SimpleDateFormat("MMMM d yyyy HH:mm:ss",Locale.ENGLISH)
224                                         ,new SimpleDateFormat("MMMM d yyyy HH:mm",Locale.ENGLISH)
225                                         ,new SimpleDateFormat("EEE, MMM dd, yyyy HH:mm:ssZ",Locale.ENGLISH)
226                                         ,new SimpleDateFormat("EEE, MMM dd, yyyy HH:mm:ss",Locale.ENGLISH)
227                                     ,new SimpleDateFormat("EEEE, MMMM dd, yyyy H:mm:ss a zzz",Locale.ENGLISH)
228                                         ,new SimpleDateFormat("dd-MMM-yy HH:mm a",Locale.ENGLISH)
229                                         ,new SimpleDateFormat("dd-MMMM-yy HH:mm a",Locale.ENGLISH)
230                                          ,new SimpleDateFormat("EE, dd-MMM-yyyy HH:mm:ss zz",Locale.ENGLISH)
231                                          ,new SimpleDateFormat("EE, dd MMM yyyy HH:mm:ss zz",Locale.ENGLISH)
232                                        ,new SimpleDateFormat("EEE d, MMM yyyy HH:mm:ss zz",Locale.ENGLISH)
233                                         ,new SimpleDateFormat("dd-MMM-yyyy",Locale.ENGLISH)
234                                         ,new SimpleDateFormat("MMMM, dd yyyy HH:mm:ssZ",Locale.ENGLISH)
235                                         ,new SimpleDateFormat("MMMM, dd yyyy HH:mm:ss",Locale.ENGLISH)
236                                         ,new SimpleDateFormat("yyyy/MM/dd HH:mm:ss zz",Locale.ENGLISH)
237                                         ,new SimpleDateFormat("dd MMM yyyy HH:mm:ss zz",Locale.ENGLISH)
238                                         ,new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss 'GMT'ZZ (z)",Locale.ENGLISH)
239                                         ,new SimpleDateFormat("dd MMM, yyyy HH:mm:ss",Locale.ENGLISH)
240                                         //,new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",Locale.ENGLISH)
241                                };
242                        
243                        for(int i=0;i<df.length;i++){
244                                df[i].setLenient(lenient);
245                                df[i].setTimeZone(tz);
246                        }
247                        formats.put(id, df);
248                }
249                return df;
250        }
251
252        public static DateFormat[] getFormats(Locale locale,TimeZone tz,boolean lenient, short formatType) {
253                if(FORMAT_TYPE_DATE_TIME==formatType)return getDateTimeFormats(locale,TimeZoneConstants.GMT,true);
254                if(FORMAT_TYPE_DATE==formatType)return getDateFormats(locale,TimeZoneConstants.GMT,true);
255                if(FORMAT_TYPE_TIME==formatType)return getTimeFormats(locale,TimeZoneConstants.GMT,true);
256                
257                DateFormat[] dt = getDateTimeFormats(locale,TimeZoneConstants.GMT,true);
258                DateFormat[] d = getDateFormats(locale,TimeZoneConstants.GMT,true);
259                DateFormat[] t = getTimeFormats(locale,TimeZoneConstants.GMT,true);
260                
261                DateFormat[] all=new DateFormat[dt.length+d.length+t.length];
262                for(int i=0;i<dt.length;i++){
263                        all[i]=dt[i];
264                }
265                for(int i=0;i<d.length;i++){
266                        all[i+dt.length]=d[i];
267                }
268                for(int i=0;i<t.length;i++){
269                        all[i+dt.length+d.length]=t[i];
270                }
271                return getDateTimeFormats(locale,TimeZoneConstants.GMT,true);
272        }
273
274        public static String[] getSupportedPatterns(Locale locale, short formatType) {
275                DateFormat[] _formats = getFormats(locale,TimeZoneConstants.GMT,true,formatType);
276                String[] patterns=new String[_formats.length];
277                for(int i=0;i<_formats.length;i++){
278                        if(!(_formats[i] instanceof SimpleDateFormat))return null; // all or nothing
279                        patterns[i]=((SimpleDateFormat)_formats[i]).toPattern();
280                }
281                
282                return patterns;
283        }
284        
285        
286        
287}