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.runtime.functions.displayFormatting;
020
021import java.text.DateFormatSymbols;
022import java.text.SimpleDateFormat;
023import java.util.Locale;
024import java.util.TimeZone;
025
026import lucee.commons.date.TimeZoneUtil;
027import lucee.commons.lang.StringUtil;
028import lucee.runtime.PageContext;
029import lucee.runtime.engine.ThreadLocalPageContext;
030import lucee.runtime.exp.ExpressionException;
031import lucee.runtime.exp.PageException;
032import lucee.runtime.functions.BIF;
033import lucee.runtime.op.Caster;
034import lucee.runtime.type.dt.DateTime;
035
036/**
037 * Implements the CFML Function dateformat
038 */
039public final class DateTimeFormat extends BIF {
040
041        private static final long serialVersionUID = 134840879454373440L;
042        public static final String DEFAULT_MASK = "dd-MMM-yyyy HH:mm:ss";// this is already a SimpleDateFormat mask!
043        private static final String[] AP = new String[]{"A","P"};
044
045        /**
046         * @param pc
047         * @param object
048         * @return Formated Time Object as String
049         * @throws ExpressionException
050         */
051        public static String call(PageContext pc , Object object) throws ExpressionException {
052                return invoke(pc,object, null,Locale.US,ThreadLocalPageContext.getTimeZone(pc));
053        }
054        
055        /**
056         * @param pc
057         * @param object
058         * @param mask Characters that show how CFML displays a date:
059         * @return Formated Time Object as String
060         * @throws ExpressionException
061         */
062        public static String call(PageContext pc , Object object, String mask) throws ExpressionException {
063                return invoke(pc,object,mask,Locale.US,ThreadLocalPageContext.getTimeZone(pc));
064        }
065
066        public static String call(PageContext pc , Object object, String mask,String strTimezone) throws ExpressionException {
067                return invoke(pc,object,mask, Locale.US,strTimezone==null?ThreadLocalPageContext.getTimeZone(pc):TimeZoneUtil.toTimeZone(strTimezone));
068        }
069        
070        public static String invoke(PageContext pc , Object object, String mask,Locale locale,TimeZone tz) throws ExpressionException {
071                if(locale==null) locale=Locale.US;
072                DateTime datetime = Caster.toDate(object,true,tz,null);
073                if(datetime==null) {
074                    if(object.toString().trim().length()==0) return "";
075                    throw new ExpressionException("can't convert value "+object+" to a datetime value");
076                }
077                java.text.DateFormat format=null;
078                
079                if("short".equalsIgnoreCase(mask)) 
080                        format=java.text.DateFormat.getDateTimeInstance(java.text.DateFormat.SHORT, java.text.DateFormat.SHORT, locale);
081                else if("medium".equalsIgnoreCase(mask)) 
082                        format=java.text.DateFormat.getDateTimeInstance(java.text.DateFormat.MEDIUM, java.text.DateFormat.MEDIUM, locale);
083                else if("long".equalsIgnoreCase(mask)) 
084                        format=java.text.DateFormat.getDateTimeInstance(java.text.DateFormat.LONG, java.text.DateFormat.LONG, locale);
085                else if("full".equalsIgnoreCase(mask)) 
086                        format=java.text.DateFormat.getDateTimeInstance(java.text.DateFormat.FULL, java.text.DateFormat.FULL, locale);
087                else if ("iso8601".equalsIgnoreCase(mask))
088                        format = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ssZ" );
089                else {
090                        SimpleDateFormat sdf;
091                        format = sdf= new SimpleDateFormat(convertMask(mask), locale);
092                        if(mask!=null &&  StringUtil.indexOfIgnoreCase(mask, "tt")==-1 && StringUtil.indexOfIgnoreCase(mask, "t")!=-1) {
093                                DateFormatSymbols dfs = new DateFormatSymbols(locale);
094                                dfs.setAmPmStrings(AP);
095                                sdf.setDateFormatSymbols(dfs);
096                        }
097                }
098                format.setTimeZone(tz);
099        return format.format(datetime);
100        }
101        
102        @Override
103        public Object invoke(PageContext pc, Object[] args) throws PageException {
104                if(args.length==1)return call(pc,args[0]);
105                if(args.length==2)return call(pc,args[0],Caster.toString(args[1]));
106                return call(pc,args[0],Caster.toString(args[1]),Caster.toString(args[2]));
107        }
108        
109
110
111        private static String convertMask(String mask) {
112                if(mask==null) return DEFAULT_MASK;
113                boolean inside=false;
114                char[] carr = mask.toCharArray();
115                StringBuilder sb=new StringBuilder();
116                for(int i=0;i<carr.length;i++){
117                        
118                        switch(carr[i]){
119                        case 'm': if(!inside){sb.append('M');}else{sb.append(carr[i]);} break;
120                        case 'S': if(!inside){sb.append('s');}else{sb.append(carr[i]);} break;
121                        case 't': if(!inside){sb.append('a');}else{sb.append(carr[i]);} break;
122                        case 'T': if(!inside){sb.append('a');}else{sb.append(carr[i]);} break;
123                        case 'n': if(!inside){sb.append('m');}else{sb.append(carr[i]);} break;
124                        case 'N': if(!inside){sb.append('m');}else{sb.append(carr[i]);} break;
125                        case 'l': if(!inside){sb.append('S');}else{sb.append(carr[i]);} break;
126                        case 'L': if(!inside){sb.append('S');}else{sb.append(carr[i]);} break;
127                        
128                        case 'f': if(!inside){sb.append("'f'");}else{sb.append(carr[i]);} break;
129                        case 'e': if(!inside){sb.append("'e'");}else{sb.append(carr[i]);} break;
130                        
131                        case 'G': 
132                        case 'y': 
133                        case 'M': 
134                        case 'W': 
135                        case 'w': 
136                        case 'D': 
137                        case 'd': 
138                        case 'F': 
139                        case 'E': 
140                        case 'a': 
141                        case 'H': 
142                        case 'h': 
143                        case 'K': 
144                        case 'k': 
145                        case 'Z': 
146                        case 'z': 
147                        case 's': 
148                        //case '.': 
149                                        sb.append(carr[i]);
150                        break;
151                        
152
153                        case '\'':
154                                if(carr.length-1>i) {
155                                        if(carr[i+1]=='\'') {
156                                                i++;
157                                                sb.append("''");
158                                                break;
159                                        }
160                                }
161                                
162                                
163                                inside=!inside;
164                                sb.append(carr[i]);
165                        break;
166                        /*case '\'':
167                                if(carr.length-1>i) {
168                                        if(carr[i+1]=='\'') {
169                                                i++;
170                                                sb.append("''");
171                                                break;
172                                        }
173                                }
174                                sb.append("''");
175                        break;*/
176                        default:
177                                char c=carr[i];
178                                if(!inside && ((c>='a' && c<='z') || (c>='A' && c<='Z')))
179                                        sb.append('\'').append(c).append('\'');
180                                else
181                                        sb.append(c);
182                        }
183                }
184                return sb.toString();
185        }
186}