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  }