001/*
002 * Copyright (c) 2016-2018 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.TimeZone;
031
032import cascading.CascadingException;
033import cascading.util.Util;
034
035/**
036 * Class DateCoercibleType is an implementation of {@link CoercibleType}.
037 * <p>
038 * Given a {@code dateFormatString}, using the {@link SimpleDateFormat} format, this CoercibleType
039 * will convert a value from the formatted string to a {@code Long} canonical type and back.
040 * <p>
041 * This class when presented with a Long timestamp value will assume the value is in UTC.
042 * <p>
043 * See {@link cascading.operation.text.DateParser} and {@link cascading.operation.text.DateFormatter} for similar
044 * Operations for use within a pipe assembly.
045 */
046public class DateType implements CoercibleType<Long>
047  {
048  /** Field zone */
049  protected TimeZone zone;
050  /** Field locale */
051  protected Locale locale;
052  /** Field dateFormatString */
053  protected String dateFormatString;
054  /** Field dateFormat */
055  private transient SimpleDateFormat dateFormat;
056
057  /**
058   * Create a new DateType instance.
059   *
060   * @param dateFormatString
061   * @param zone
062   * @param locale
063   */
064  public DateType( String dateFormatString, TimeZone zone, Locale locale )
065    {
066    this.zone = zone;
067    this.locale = locale;
068    this.dateFormatString = dateFormatString;
069    }
070
071  public DateType( String dateFormatString, TimeZone zone )
072    {
073    this.zone = zone;
074    this.dateFormatString = dateFormatString;
075    }
076
077  /**
078   * Create a new DateType instance.
079   *
080   * @param dateFormatString
081   */
082  public DateType( String dateFormatString )
083    {
084    this.dateFormatString = dateFormatString;
085    }
086
087  @Override
088  public Class getCanonicalType()
089    {
090    return Long.TYPE;
091    }
092
093  public SimpleDateFormat getDateFormat()
094    {
095    if( dateFormat != null )
096      return dateFormat;
097
098    dateFormat = new SimpleDateFormat( dateFormatString, getLocale() );
099
100    dateFormat.setTimeZone( getZone() );
101
102    return dateFormat;
103    }
104
105  private Locale getLocale()
106    {
107    if( locale != null )
108      return locale;
109
110    return Locale.getDefault();
111    }
112
113  private TimeZone getZone()
114    {
115    if( zone != null )
116      return zone;
117
118    return TimeZone.getTimeZone( "UTC" );
119    }
120
121  protected Calendar getCalendar()
122    {
123    return Calendar.getInstance( TimeZone.getTimeZone( "UTC" ), getLocale() );
124    }
125
126  @Override
127  public Long canonical( Object value )
128    {
129    if( value == null )
130      return null;
131
132    Class from = value.getClass();
133
134    if( from == String.class )
135      return parse( (String) value ).getTime();
136
137    if( from == Date.class )
138      return ( (Date) value ).getTime(); // in UTC
139
140    if( from == Long.class || from == long.class )
141      return (Long) value;
142
143    throw new CascadingException( "unknown type coercion requested from: " + Util.getTypeName( from ) );
144    }
145
146  @Override
147  public Object coerce( Object value, Type to )
148    {
149    if( value == null )
150      return null;
151
152    Class from = value.getClass();
153
154    if( from != Long.class )
155      throw new IllegalStateException( "was not normalized" );
156
157    // no coercion, or already in canonical form
158    if( to == Long.class || to == long.class || to == Object.class || DateType.class == to.getClass() )
159      return value;
160
161    if( to == String.class )
162      {
163      Calendar calendar = getCalendar();
164
165      calendar.setTimeInMillis( (Long) value );
166
167      return getDateFormat().format( calendar.getTime() );
168      }
169
170    throw new CascadingException( "unknown type coercion requested, from: " + Util.getTypeName( from ) + " to: " + Util.getTypeName( to ) );
171    }
172
173  private Date parse( String value )
174    {
175    try
176      {
177      return getDateFormat().parse( value );
178      }
179    catch( ParseException exception )
180      {
181      throw new CascadingException( "unable to parse value: " + value + " with format: " + dateFormatString );
182      }
183    }
184
185  @Override
186  public String toString()
187    {
188    final StringBuilder sb = new StringBuilder( "DateType{" );
189    sb.append( "dateFormatString='" ).append( dateFormatString ).append( '\'' );
190    sb.append( "," );
191    sb.append( "canonicalType='" ).append( getCanonicalType() ).append( '\'' );
192    sb.append( '}' );
193    return sb.toString();
194    }
195  }