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