001/* 002 * Copyright (c) 2016-2019 Chris K Wensel <chris@wensel.net>. All Rights Reserved. 003 * Copyright (c) 2007-2017 Xplenty, Inc. All Rights Reserved. 004 * 005 * Project and contact information: http://www.cascading.org/ 006 * 007 * This file is part of the Cascading project. 008 * 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 */ 021 022package cascading.tuple.type; 023 024import java.lang.reflect.Type; 025import java.text.ParseException; 026import java.text.SimpleDateFormat; 027import java.util.Calendar; 028import java.util.Date; 029import java.util.Locale; 030import java.util.Objects; 031import java.util.TimeZone; 032 033import cascading.CascadingException; 034import cascading.util.Util; 035 036/** 037 * Class DateCoercibleType is an implementation of {@link CoercibleType}. 038 * <p> 039 * Given a {@code dateFormatString}, using the {@link SimpleDateFormat} format, this CoercibleType 040 * will convert a value from the formatted string to a {@code Long} canonical type and back. 041 * <p> 042 * This class when presented with a Long timestamp value will assume the value is in UTC. 043 * <p> 044 * See {@link cascading.operation.text.DateParser} and {@link cascading.operation.text.DateFormatter} for similar 045 * Operations for use within a pipe assembly. 046 */ 047public class DateType implements CoercibleType<Long> 048 { 049 /** Field zone */ 050 protected TimeZone zone; 051 /** Field locale */ 052 protected Locale locale; 053 /** Field dateFormatString */ 054 protected String dateFormatString; 055 /** Field dateFormat */ 056 private transient SimpleDateFormat dateFormat; 057 058 /** 059 * Create a new DateType instance. 060 * 061 * @param dateFormatString 062 * @param zone 063 * @param locale 064 */ 065 public DateType( String dateFormatString, TimeZone zone, Locale locale ) 066 { 067 this.zone = zone; 068 this.locale = locale; 069 this.dateFormatString = dateFormatString; 070 } 071 072 public DateType( String dateFormatString, TimeZone zone ) 073 { 074 this.zone = zone; 075 this.dateFormatString = dateFormatString; 076 } 077 078 /** 079 * Create a new DateType instance. 080 * 081 * @param dateFormatString 082 */ 083 public DateType( String dateFormatString ) 084 { 085 this.dateFormatString = dateFormatString; 086 } 087 088 @Override 089 public Class getCanonicalType() 090 { 091 return Long.TYPE; 092 } 093 094 public SimpleDateFormat getDateFormat() 095 { 096 if( dateFormat != null ) 097 return dateFormat; 098 099 dateFormat = new SimpleDateFormat( dateFormatString, getLocale() ); 100 101 dateFormat.setTimeZone( getZone() ); 102 103 return dateFormat; 104 } 105 106 private Locale getLocale() 107 { 108 if( locale != null ) 109 return locale; 110 111 return Locale.getDefault(); 112 } 113 114 private TimeZone getZone() 115 { 116 if( zone != null ) 117 return zone; 118 119 return TimeZone.getTimeZone( "UTC" ); 120 } 121 122 protected Calendar getCalendar() 123 { 124 return Calendar.getInstance( TimeZone.getTimeZone( "UTC" ), getLocale() ); 125 } 126 127 @Override 128 public Long canonical( Object value ) 129 { 130 if( value == null ) 131 return null; 132 133 Class from = value.getClass(); 134 135 if( from == String.class ) 136 return parse( (String) value ).getTime(); 137 138 if( from == Date.class ) 139 return ( (Date) value ).getTime(); // in UTC 140 141 if( from == Long.class || from == long.class ) 142 return (Long) value; 143 144 throw new CascadingException( "unknown type coercion requested from: " + Util.getTypeName( from ) ); 145 } 146 147 @Override 148 public Object coerce( Object value, Type to ) 149 { 150 if( value == null ) 151 return null; 152 153 Class from = value.getClass(); 154 155 if( from != Long.class ) 156 throw new IllegalStateException( "was not normalized" ); 157 158 // no coercion, or already in canonical form 159 if( to == Long.class || to == long.class || to == Object.class || DateType.class == to.getClass() ) 160 return value; 161 162 if( to == String.class ) 163 { 164 Calendar calendar = getCalendar(); 165 166 calendar.setTimeInMillis( (Long) value ); 167 168 return getDateFormat().format( calendar.getTime() ); 169 } 170 171 throw new CascadingException( "unknown type coercion requested, from: " + Util.getTypeName( from ) + " to: " + Util.getTypeName( to ) ); 172 } 173 174 private Date parse( String value ) 175 { 176 try 177 { 178 return getDateFormat().parse( value ); 179 } 180 catch( ParseException exception ) 181 { 182 throw new CascadingException( "unable to parse value: " + value + " with format: " + dateFormatString ); 183 } 184 } 185 186 @Override 187 public boolean equals( Object object ) 188 { 189 if( this == object ) 190 return true; 191 if( !( object instanceof DateType ) ) 192 return false; 193 DateType dateType = (DateType) object; 194 return Objects.equals( zone, dateType.zone ) && 195 Objects.equals( locale, dateType.locale ) && 196 Objects.equals( dateFormatString, dateType.dateFormatString ); 197 } 198 199 @Override 200 public int hashCode() 201 { 202 return Objects.hash( zone, locale, dateFormatString ); 203 } 204 205 @Override 206 public String toString() 207 { 208 final StringBuilder sb = new StringBuilder( "DateType{" ); 209 sb.append( "dateFormatString='" ).append( dateFormatString ).append( '\'' ); 210 sb.append( "," ); 211 sb.append( "canonicalType='" ).append( getCanonicalType() ).append( '\'' ); 212 sb.append( '}' ); 213 return sb.toString(); 214 } 215 }