diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/api/twitter/util/ThreadLocalSimpleDateFormat.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/api/twitter/util/ThreadLocalSimpleDateFormat.java new file mode 100644 index 000000000..d6a8a84d5 --- /dev/null +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/api/twitter/util/ThreadLocalSimpleDateFormat.java @@ -0,0 +1,102 @@ +package org.mariotaku.twidere.api.twitter.util; + +import android.support.annotation.NonNull; + +import java.text.AttributedCharacterIterator; +import java.text.DateFormat; +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Created by mariotaku on 16/4/5. + */ +public final class ThreadLocalSimpleDateFormat extends DateFormat { + + private final ThreadLocal threadLocal; + + public ThreadLocalSimpleDateFormat(@NonNull final String pattern, final Locale locale) { + threadLocal = new ThreadLocal() { + @Override + protected SimpleDateFormat initialValue() { + return new SimpleDateFormat(pattern, locale); + } + }; + } + + @Override + public void setTimeZone(TimeZone timezone) { + threadLocal.get().setTimeZone(timezone); + } + + @Override + public void setNumberFormat(NumberFormat format) { + threadLocal.get().setNumberFormat(format); + } + + @Override + public void setLenient(boolean value) { + threadLocal.get().setLenient(value); + } + + @Override + public void setCalendar(Calendar cal) { + threadLocal.get().setCalendar(cal); + } + + @Override + public Object parseObject(String string, @NonNull ParsePosition position) { + return threadLocal.get().parseObject(string, position); + } + + @Override + public Date parse(String string) throws ParseException { + return threadLocal.get().parse(string); + } + + @Override + public boolean isLenient() { + return threadLocal.get().isLenient(); + } + + @Override + public TimeZone getTimeZone() { + return threadLocal.get().getTimeZone(); + } + + @Override + public NumberFormat getNumberFormat() { + return threadLocal.get().getNumberFormat(); + } + + @Override + public Calendar getCalendar() { + return threadLocal.get().getCalendar(); + } + + @Override + public AttributedCharacterIterator formatToCharacterIterator(@NonNull Object object) { + return threadLocal.get().formatToCharacterIterator(object); + } + + @Override + public Object parseObject(String string) throws ParseException { + return threadLocal.get().parseObject(string); + } + + @Override + public StringBuffer format(Date date, StringBuffer buffer, FieldPosition field) { + return threadLocal.get().format(date, buffer, field); + } + + @Override + public Date parse(String string, ParsePosition position) { + return threadLocal.get().parse(string, position); + } +} diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/api/twitter/util/TwitterDateConverter.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/api/twitter/util/TwitterDateConverter.java index c8bdd9829..edfe04650 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/api/twitter/util/TwitterDateConverter.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/api/twitter/util/TwitterDateConverter.java @@ -19,143 +19,41 @@ package org.mariotaku.twidere.api.twitter.util; -import android.util.Log; - import com.bluelinelabs.logansquare.typeconverters.StringBasedTypeConverter; import java.text.DateFormat; import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; import java.util.Date; -import java.util.GregorianCalendar; -import java.util.List; import java.util.Locale; -import java.util.SimpleTimeZone; import java.util.TimeZone; -import java.util.concurrent.TimeUnit; /** * Created by mariotaku on 15/5/7. */ public class TwitterDateConverter extends StringBasedTypeConverter { - private static final String[] WEEK_NAMES = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; - private static final String[] MONTH_NAMES = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", - "Aug", "Sep", "Oct", "Nov", "Dec"}; + static final DateFormat sFormat = new ThreadLocalSimpleDateFormat("EEE MMM dd HH:mm:ss ZZZZZ yyyy", + Locale.ENGLISH); - private static final TimeZone TIME_ZONE = TimeZone.getTimeZone("UTC"); - private static final Locale LOCALE = Locale.ENGLISH; - private final DateFormat mDateFormat; - - public TwitterDateConverter() { - mDateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss ZZZZZ yyyy", LOCALE); - mDateFormat.setLenient(true); - mDateFormat.setTimeZone(TIME_ZONE); + static { + sFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + sFormat.setLenient(true); } @Override - public Date getFromString(String string) { - Date date = null; + public Date getFromString(final String string) { + if (string == null) return null; try { - date = parseTwitterDate(string); - } catch (NumberFormatException e) { - Log.w("Twidere", e); - // Ignore - } - if (date != null) return date; - try { - date = mDateFormat.parse(string); + return sFormat.parse(string); } catch (ParseException e) { return null; } - return date; - } - - private Date parseTwitterDate(String string) { - final List segs = split(string, " "); - if (segs.size() != 6) { - return null; - } - final List timeSegs = split(segs.get(3), ":"); - if (timeSegs.size() != 3) { - return null; - } - - final GregorianCalendar calendar = new GregorianCalendar(TIME_ZONE, LOCALE); - calendar.clear(); - final int monthIdx = indexOf(MONTH_NAMES, segs.get(1)); - if (monthIdx < 0) { - return null; - } - calendar.set(Calendar.YEAR, Integer.parseInt(segs.get(5))); - calendar.set(Calendar.MONTH, monthIdx); - calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(segs.get(2))); - calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(timeSegs.get(0))); - calendar.set(Calendar.MINUTE, Integer.parseInt(timeSegs.get(1))); - calendar.set(Calendar.SECOND, Integer.parseInt(timeSegs.get(2))); - calendar.setTimeZone(SimpleTimeZone.getTimeZone(getTimezoneText(segs.get(4)))); - final Date date = calendar.getTime(); - if (!WEEK_NAMES[calendar.get(Calendar.DAY_OF_WEEK) - 1].equals(segs.get(0))) { - return null; - } - return date; } @Override - public String convertToString(Date date) { - final Calendar calendar = Calendar.getInstance(); - calendar.setTime(date); - final StringBuilder sb = new StringBuilder(); - sb.append(WEEK_NAMES[calendar.get(Calendar.DAY_OF_WEEK) - 1]); - sb.append(' '); - sb.append(MONTH_NAMES[calendar.get(Calendar.MONTH)]); - sb.append(' '); - sb.append(calendar.get(Calendar.DAY_OF_MONTH)); - sb.append(' '); - sb.append(calendar.get(Calendar.HOUR_OF_DAY)); - sb.append(':'); - sb.append(calendar.get(Calendar.MINUTE)); - sb.append(':'); - sb.append(calendar.get(Calendar.SECOND)); - sb.append(' '); - final long offset = TimeUnit.MILLISECONDS.toMinutes(calendar.get(Calendar.ZONE_OFFSET)); - sb.append(offset > 0 ? '+' : '-'); - sb.append(String.format(Locale.ROOT, "%02d%02d", Math.abs(offset) / 60, Math.abs(offset) % 60)); - sb.append(' '); - sb.append(calendar.get(Calendar.YEAR)); - return sb.toString(); - } - - private String getTimezoneText(String seg) { - if (seg.startsWith("GMT") || seg.startsWith("UTC")) return seg; - return "GMT" + seg; - } - - public static List split(String input, String delim) { - List l = new ArrayList<>(); - int offset = 0; - - while (true) { - int index = input.indexOf(delim, offset); - if (index == -1) { - l.add(input.substring(offset)); - return l; - } else { - l.add(input.substring(offset, index)); - offset = (index + delim.length()); - } - } - } - - private static int indexOf(String[] input, String find) { - for (int i = 0, inputLength = input.length; i < inputLength; i++) { - if (find == null) { - if (input[i] == null) return i; - } else if (find.equals(input[i])) return i; - } - return -1; + public String convertToString(final Date date) { + if (date == null) return null; + return sFormat.format(date); } } diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/UserKey.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/UserKey.java index 4fcab544d..b5a18c73c 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/UserKey.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/UserKey.java @@ -10,8 +10,7 @@ import com.bluelinelabs.logansquare.annotation.JsonObject; import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease; import com.hannesdorfmann.parcelableplease.annotation.ParcelableThisPlease; -import org.mariotaku.twidere.api.twitter.util.TwitterDateConverter; - +import java.util.ArrayList; import java.util.List; /** @@ -164,7 +163,7 @@ public class UserKey implements Comparable, Parcelable { @Nullable public static UserKey[] arrayOf(@Nullable String str) { if (str == null) return null; - List split = TwitterDateConverter.split(str, ","); + List split = split(str, ","); UserKey[] keys = new UserKey[split.size()]; for (int i = 0, splitLength = split.size(); i < splitLength; i++) { keys[i] = valueOf(split.get(i)); @@ -181,7 +180,6 @@ public class UserKey implements Comparable, Parcelable { return result; } - public static String escapeText(String host) { final StringBuilder sb = new StringBuilder(); for (int i = 0, j = host.length(); i < j; i++) { @@ -194,6 +192,7 @@ public class UserKey implements Comparable, Parcelable { return sb.toString(); } + private static boolean isSpecialChar(char ch) { return ch == '\\' || ch == '@' || ch == ','; } @@ -201,4 +200,20 @@ public class UserKey implements Comparable, Parcelable { public boolean maybeEquals(@Nullable UserKey another) { return another != null && another.getId().equals(id); } + + public static List split(String input, String delim) { + List l = new ArrayList<>(); + int offset = 0; + + while (true) { + int index = input.indexOf(delim, offset); + if (index == -1) { + l.add(input.substring(offset)); + return l; + } else { + l.add(input.substring(offset, index)); + offset = index + delim.length(); + } + } + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/card/CardPollFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/card/CardPollFragment.java index 9bcc55ddc..874abb382 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/card/CardPollFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/card/CardPollFragment.java @@ -45,6 +45,8 @@ import android.widget.TableLayout; import android.widget.TextView; import org.apache.commons.lang3.math.NumberUtils; +import org.mariotaku.abstask.library.AbstractTask; +import org.mariotaku.abstask.library.TaskStarter; import org.mariotaku.twidere.R; import org.mariotaku.twidere.api.twitter.TwitterCaps; import org.mariotaku.twidere.api.twitter.TwitterException; @@ -55,12 +57,11 @@ import org.mariotaku.twidere.model.ParcelableCardEntity; import org.mariotaku.twidere.model.ParcelableStatus; import org.mariotaku.twidere.model.UserKey; import org.mariotaku.twidere.model.util.ParcelableCardEntityUtils; -import org.mariotaku.abstask.library.AbstractTask; -import org.mariotaku.abstask.library.TaskStarter; import org.mariotaku.twidere.util.TwitterAPIFactory; import org.mariotaku.twidere.util.support.ViewSupport; import java.util.Date; +import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -190,6 +191,7 @@ public class CardPollFragment extends BaseSupportFragment implements return null; } }; + task.setResultHandler(CardPollFragment.this); task.setParams(cardData); TaskStarter.execute(task); } @@ -212,7 +214,7 @@ public class CardPollFragment extends BaseSupportFragment implements if (label == null) throw new NullPointerException(); final float choicePercent = votesSum == 0 ? 0 : value / (float) votesSum; choiceLabelView.setText(label); - choicePercentView.setText(String.format("%d%%", Math.round(choicePercent * 100))); + choicePercentView.setText(String.format(Locale.US, "%d%%", Math.round(choicePercent * 100))); pollItem.setOnClickListener(clickListener); diff --git a/twidere/src/main/java/org/mariotaku/twidere/model/util/ParcelableCardEntityUtils.java b/twidere/src/main/java/org/mariotaku/twidere/model/util/ParcelableCardEntityUtils.java index 6b0c24ca7..854af2fed 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/model/util/ParcelableCardEntityUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/model/util/ParcelableCardEntityUtils.java @@ -8,18 +8,30 @@ import android.support.v4.util.ArrayMap; import org.apache.commons.lang3.math.NumberUtils; import org.mariotaku.twidere.TwidereConstants; import org.mariotaku.twidere.api.twitter.model.CardEntity; +import org.mariotaku.twidere.api.twitter.util.ThreadLocalSimpleDateFormat; import org.mariotaku.twidere.model.ParcelableCardEntity; import org.mariotaku.twidere.model.UserKey; -import org.mariotaku.twidere.util.InternalParseUtils; +import java.text.DateFormat; +import java.text.ParseException; import java.util.Date; +import java.util.Locale; import java.util.Map; +import java.util.TimeZone; /** * Created by mariotaku on 16/2/24. */ public class ParcelableCardEntityUtils implements TwidereConstants { + static final DateFormat sISOFormat = new ThreadLocalSimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", + Locale.ENGLISH); + + static { + sISOFormat.setLenient(true); + sISOFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + } + @Nullable public static ParcelableCardEntity fromCardEntity(@Nullable CardEntity card, @Nullable UserKey accountKey) { if (card == null) return null; @@ -74,8 +86,11 @@ public class ParcelableCardEntityUtils implements TwidereConstants { public static Date getAsDate(@NonNull ParcelableCardEntity obj, @NonNull String key, Date def) { final ParcelableCardEntity.ParcelableBindingValue value = obj.getValue(key); if (value == null) return def; - final String str = value.value; - return InternalParseUtils.parseISODateTime(str, def); + try { + return sISOFormat.parse(value.value); + } catch (ParseException e) { + return def; + } } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/InternalParseUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/InternalParseUtils.java index 165301d07..30aeccd47 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/InternalParseUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/InternalParseUtils.java @@ -4,15 +4,11 @@ import android.os.Bundle; import android.util.JsonWriter; import android.util.Log; -import org.apache.commons.lang3.time.DateFormatUtils; -import org.apache.commons.lang3.time.DateUtils; import org.mariotaku.restfu.RestFuUtils; import org.mariotaku.twidere.TwidereConstants; import java.io.IOException; import java.io.StringWriter; -import java.text.ParseException; -import java.util.Date; import java.util.Locale; import java.util.Set; @@ -71,18 +67,4 @@ public class InternalParseUtils { return result.substring(0, i == dotIdx ? dotIdx : i + 1); } - public static Date parseISODateTime(String str, Date def) { - try { - return DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.parse(str); - } catch (ParseException e) { - return def; - } catch (Error nsme) { - // Fuck Xiaomi http://crashes.to/s/a84a3d257dc - try { - return DateUtils.parseDate(str, Locale.ENGLISH, "yyyy-MM-dd'T'HH:mm:ssZZ"); - } catch (ParseException e1) { - return def; - } - } - } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java index 29819f611..7681c761e 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java @@ -2303,7 +2303,6 @@ public final class Utils implements Constants { public static boolean checkDeviceCompatible() { try { Menu.class.isAssignableFrom(MenuBuilder.class); - InternalParseUtils.parseISODateTime("2001-01-01T01:01:01Z", null); } catch (Error e) { TwidereBugReporter.logException(e); return false;