Skip to content

Commit

Permalink
Enhance the NSTimeZone-backed TimeZone with tests
Browse files Browse the repository at this point in the history
	Change on 2016/06/08 by lukhnos <lukhnos@google.com>

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=124395803
  • Loading branch information
lukhnos authored and Keith Stanger committed Jun 24, 2016
1 parent 4522cc2 commit 3242d25
Show file tree
Hide file tree
Showing 9 changed files with 1,785 additions and 765 deletions.
243 changes: 243 additions & 0 deletions jre_emul/Classes/com/google/j2objc/util/NativeTimeZone.java
@@ -0,0 +1,243 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.j2objc.util;

import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

/*-[
#import "IOSClass.h"
#import "java/lang/IllegalArgumentException.h"
#import "java/util/GregorianCalendar.h"
#import "java/util/GregorianCalendar.h"
]-*/

/**
* An NSTimeZone-backed concrete TimeZone implementation that provides daylight saving time (DST)
* and historical time zone offsets from the native iOS/OS X time zone database.
*
* @author Lukhnos Liu
*/
public final class NativeTimeZone extends TimeZone {

private final Object nativeTimeZone;
private final int rawOffset;
private final int dstSavings;
private final boolean useDaylightTime;

public static native String[] getAvailableNativeTimeZoneNames() /*-[
NSArray *timeZones = [NSTimeZone knownTimeZoneNames];
return [IOSObjectArray arrayWithNSArray:timeZones type:NSString_class_()];
]-*/;


public static native NativeTimeZone get(String name) /*-[
NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:name];
if (timeZone == nil) {
return nil;
}
return [ComGoogleJ2objcUtilNativeTimeZone getWithNativeTimeZoneWithId:timeZone];
]-*/;

public static native NativeTimeZone getDefaultNativeTimeZone() /*-[
NSTimeZone *timeZone = [NSTimeZone defaultTimeZone];
if (timeZone == nil) { // Unlikely, but just to be defensive.
return nil;
}
return [ComGoogleJ2objcUtilNativeTimeZone getWithNativeTimeZoneWithId:timeZone];
]-*/;

public static native NativeTimeZone getWithNativeTimeZone(Object nativeTimeZone) /*-[
NSTimeZone *tz = (NSTimeZone *)nativeTimeZone;
NSDate *now = [NSDate date];
NSInteger offset = [tz secondsFromGMTForDate:now];
NSTimeInterval dstOffset = [tz daylightSavingTimeOffsetForDate:now];
// The DST offset is relative to the current offset, and hence the math here.
jint rawOffset = (jint)(offset * 1000) - (jint)(dstOffset * 1000.0);
NSDate *nextTransition = [tz nextDaylightSavingTimeTransitionAfterDate:now];
jint dstSavings;
jboolean useDaylightTime;
if (nextTransition) {
NSTimeInterval nextDstOffset = [tz daylightSavingTimeOffsetForDate:nextTransition];
// This is a simplified assumption. Technically, there's nothing in the TZ rules
// that says you can't have a +1 transition tomorrow, and a +2 the day after. This
// is why in more modern time libraries, there is no longer the notion of a fixed
// DST offset.
NSTimeInterval fixedDstOffset = (dstOffset != 0) ? dstOffset : nextDstOffset;
// And the offset is always positive regardless the hemisphere the TZ is in.
dstSavings = fabs(fixedDstOffset) * 1000;
useDaylightTime = true;
} else {
dstSavings = 0;
useDaylightTime = false;
}
return
create_ComGoogleJ2objcUtilNativeTimeZone_initWithId_withNSString_withInt_withInt_withBoolean_(
nativeTimeZone, tz.name, rawOffset, dstSavings, useDaylightTime);
]-*/;

/**
* Create an NSTimeZone-backed TimeZone instance.
*
* @param nativeTimeZone the NSTimeZone instance.
* @param name the native time zone's name.
* @param rawOffset the pre-calculated raw offset (in millis) from UTC. When TimeZone was
* designed, the assumption was that the rawOffset would be a constant at
* all times. We pre-compute this offset using the instant when the
* instance is created.
* @param useDaylightTime whether this time zone observes DST at the moment this instance
* is created.
*/
private NativeTimeZone(Object nativeTimeZone, String name, int rawOffset, int dstSavings,
boolean useDaylightTime) {
setID(name);
this.nativeTimeZone = nativeTimeZone;
this.rawOffset = rawOffset;
this.dstSavings = dstSavings;
this.useDaylightTime = useDaylightTime;
}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof NativeTimeZone)) {
return false;
}

return nativeTimeZone.equals(((NativeTimeZone) obj).nativeTimeZone);
}

@Override
public native int hashCode() /*-[
return [nativeTimeZone_ hash];
]-*/;

@Override
public boolean hasSameRules(TimeZone other) {
if (other instanceof NativeTimeZone) {
return compareNativeTimeZoneRules(((NativeTimeZone) other).nativeTimeZone);
}
return super.hasSameRules(other);
}

@Override
public native int getOffset(long time) /*-[
double interval = (double)time / 1000.0;
NSDate *date = [NSDate dateWithTimeIntervalSince1970:interval];
return (jint)[(NSTimeZone *)nativeTimeZone_ secondsFromGMTForDate:date] * 1000;
]-*/;

@Override
public native int getOffset(int era, int year, int month, int day, int dayOfWeek,
int milliseconds) /*-[
if (era != JavaUtilGregorianCalendar_BC && era != JavaUtilGregorianCalendar_AD) {
@throw [[[JavaLangIllegalArgumentException alloc]
initWithNSString:@"Only GregorianCalendar.BCE or GregorianCalendar.AD allowed"]
autorelease];
}
// The local datetime used here is always in the non-DST time zone, i.e. the time zone
// with the "raw" offset, as evidenced by actual JDK and Android implementations. This
// means it's possible to getOffset from a practically non-existent date time, such as
// 2:30 AM, March 13, 2016, which does not exist in US Pacific Time -- it falls in the
// DST gap of that day. The accommodation here is for quirk-compatibility.
NSCalendar *calendar = [[[NSCalendar alloc]
initWithCalendarIdentifier:NSCalendarIdentifierGregorian] autorelease];
calendar.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:rawOffset_ / 1000];
NSInteger nano = (milliseconds % 1000) * 1000000;
NSInteger sec = (milliseconds / 1000) % 60;
NSInteger min = (milliseconds / 60000) % 60;
NSInteger hour = milliseconds / 3600000;
// Recall JDK Calendar uses 0-based month. dayOfWeek is ignored here.
NSDate *date = [calendar dateWithEra:era year:year month:(month + 1) day:day hour:hour
minute:min second:sec nanosecond:nano];
return (jint)[(NSTimeZone *)nativeTimeZone_ secondsFromGMTForDate:date] * 1000;
]-*/;

@Override
public int getRawOffset() {
return rawOffset;
}

@Override
public void setRawOffset(int offsetMillis) {
throw new UnsupportedOperationException("Cannot set raw offset on a native TimeZone");
}

@Override
public int getDSTSavings() {
return dstSavings;
}

@Override
public boolean useDaylightTime() {
return useDaylightTime;
}

@Override
public boolean inDaylightTime(Date date) {
return getOffset(date.getTime()) != rawOffset;
}

@Override
public native String getDisplayName(boolean daylight, int style, Locale locale) /*-[
if (style != JavaUtilTimeZone_SHORT && style != JavaUtilTimeZone_LONG) {
@throw [[[JavaLangIllegalArgumentException alloc] init] autorelease];
}
NSTimeZoneNameStyle zoneStyle;
// "daylight" is defined in <time.h>, hence the renaming.
if (daylight_ && useDaylightTime_) {
zoneStyle = (style == JavaUtilTimeZone_SHORT) ?
NSTimeZoneNameStyleShortDaylightSaving : NSTimeZoneNameStyleDaylightSaving;
} else {
zoneStyle = (style == JavaUtilTimeZone_SHORT) ?
NSTimeZoneNameStyleShortStandard : NSTimeZoneNameStyleStandard;
}
// Find native locale.
NSLocale *nativeLocale;
if (locale) {
NSMutableDictionary *components = [NSMutableDictionary dictionary];
[components setObject:[locale getLanguage] forKey:NSLocaleLanguageCode];
[components setObject:[locale getCountry] forKey:NSLocaleCountryCode];
[components setObject:[locale getVariant] forKey:NSLocaleVariantCode];
NSString *localeId = [NSLocale localeIdentifierFromComponents:components];
nativeLocale = AUTORELEASE([[NSLocale alloc] initWithLocaleIdentifier:localeId]);
} else {
nativeLocale = [NSLocale currentLocale];
}
return [(NSTimeZone *) nativeTimeZone_ localizedName:zoneStyle locale:nativeLocale];
]-*/;

private native boolean compareNativeTimeZoneRules(Object otherNativeTimeZone) /*-[
// [NSTimeZone isEqualToTimeZone:] also compares names, which we don't want. Since we
// only deal with native time zones that can be obtained with known names, we'll just
// compare the underlying data.
NSTimeZone *other = (NSTimeZone *)otherNativeTimeZone;
return [((NSTimeZone *)self->nativeTimeZone_).data isEqualToData:other.data];
]-*/;
}
22 changes: 22 additions & 0 deletions jre_emul/JreEmulation.xcodeproj/project.pbxproj
Expand Up @@ -638,6 +638,9 @@
514171471BED5CAE0044F0FD /* MockHomonymySubjectBeanInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 514171461BED5CAE0044F0FD /* MockHomonymySubjectBeanInfo.m */; };
514171491BED5CD70044F0FD /* MockHomonymySubjectBeanInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 514171481BED5CD70044F0FD /* MockHomonymySubjectBeanInfo.m */; };
6AC258CB1CB4CE1F00AF82C9 /* LinkedListTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6AC258CA1CB4CDA200AF82C9 /* LinkedListTest.m */; };
6AF8A35D1D07763800B4CA82 /* NativeTimeZoneTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6AF8A35C1D07761D00B4CA82 /* NativeTimeZoneTest.m */; };
6AF8A3621D0776D000B4CA82 /* StringTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6AF8A3611D07769100B4CA82 /* StringTest.m */; };
6AF8A3631D0776D900B4CA82 /* RetainedWithTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6AF8A35F1D07769100B4CA82 /* RetainedWithTest.m */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -4041,6 +4044,14 @@
51719E5318EEDDCE006DEEB7 /* IOSPrimitiveArray.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IOSPrimitiveArray.h; sourceTree = "<group>"; };
51719E5418EEDDCE006DEEB7 /* IOSPrimitiveArray.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IOSPrimitiveArray.m; sourceTree = "<group>"; };
6AC258CA1CB4CDA200AF82C9 /* LinkedListTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = LinkedListTest.m; path = com/google/j2objc/LinkedListTest.m; sourceTree = "<group>"; };
6AF8A35B1D07761D00B4CA82 /* NativeTimeZoneTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NativeTimeZoneTest.h; path = com/google/j2objc/util/NativeTimeZoneTest.h; sourceTree = "<group>"; };
6AF8A35C1D07761D00B4CA82 /* NativeTimeZoneTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NativeTimeZoneTest.m; path = com/google/j2objc/util/NativeTimeZoneTest.m; sourceTree = "<group>"; };
6AF8A35E1D07769100B4CA82 /* RetainedWithTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RetainedWithTest.h; path = com/google/j2objc/RetainedWithTest.h; sourceTree = "<group>"; };
6AF8A35F1D07769100B4CA82 /* RetainedWithTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RetainedWithTest.m; path = com/google/j2objc/RetainedWithTest.m; sourceTree = "<group>"; };
6AF8A3601D07769100B4CA82 /* StringTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StringTest.h; path = com/google/j2objc/StringTest.h; sourceTree = "<group>"; };
6AF8A3611D07769100B4CA82 /* StringTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = StringTest.m; path = com/google/j2objc/StringTest.m; sourceTree = "<group>"; };
6AF8A60A1D08B42F00B4CA82 /* NativeTimeZone.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NativeTimeZone.h; path = build_result/Classes/com/google/j2objc/util/NativeTimeZone.h; sourceTree = "<group>"; };
6AF8A60B1D08B42F00B4CA82 /* NativeTimeZone.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NativeTimeZone.m; path = build_result/Classes/com/google/j2objc/util/NativeTimeZone.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -5521,6 +5532,8 @@
068677D3166C342C005A5AD7 /* NavigableMap.m */,
068677D4166C342C005A5AD7 /* NavigableSet.h */,
068677D5166C342C005A5AD7 /* NavigableSet.m */,
6AF8A60A1D08B42F00B4CA82 /* NativeTimeZone.h */,
6AF8A60B1D08B42F00B4CA82 /* NativeTimeZone.m */,
0649A69C1AE0780500DE2494 /* NClob.h */,
0649A69D1AE0780500DE2494 /* NClob.m */,
06721B4D1559CC0300645E1B /* NegativeArraySizeException.h */,
Expand Down Expand Up @@ -6712,6 +6725,8 @@
06574A301A8EB04A000A3616 /* MyMessageDigest1.m */,
06574A311A8EB04A000A3616 /* MySignature1.m */,
06979BA31C220A0C00803967 /* NativeDecimalFormatTest.m */,
6AF8A35B1D07761D00B4CA82 /* NativeTimeZoneTest.h */,
6AF8A35C1D07761D00B4CA82 /* NativeTimeZoneTest.m */,
06E4844E1C99EB1B00ABD06B /* NativeUtil.m */,
06FADA7C1B3B29B300A9579E /* NoPackageTest.h */,
06FADA7D1B3B29B300A9579E /* NoPackageTest.m */,
Expand Down Expand Up @@ -6762,6 +6777,8 @@
06C54AD71A40C897004B3ACD /* ReflectionTest.m */,
06DCD8C81CD7CAE100B855AF /* ReflectionTests.h */,
06DCD8C91CD7CAE100B855AF /* ReflectionTests.m */,
6AF8A35E1D07769100B4CA82 /* RetainedWithTest.h */,
6AF8A35F1D07769100B4CA82 /* RetainedWithTest.m */,
06574A761A8EB150000A3616 /* RSAKeyGenParameterSpecTest.m */,
06574A651A8EB150000A3616 /* RSAKeyTest.m */,
06574A771A8EB150000A3616 /* RSAMultiPrimePrivateCrtKeySpecTest.m */,
Expand Down Expand Up @@ -6801,6 +6818,8 @@
06C54ACA1A40C0D8004B3ACD /* StrictMathTest.m */,
06E685D71A2FC550006F8791 /* StringBuilderTest.h */,
06E685D81A2FC550006F8791 /* StringBuilderTest.m */,
6AF8A3601D07769100B4CA82 /* StringTest.h */,
6AF8A3611D07769100B4CA82 /* StringTest.m */,
0600C00E1B715CB4005FDEF9 /* SuperMethodReferenceTest.h */,
0600C00F1B715CB4005FDEF9 /* SuperMethodReferenceTest.m */,
06574A7E1A8EB150000A3616 /* Support_Configuration.m */,
Expand Down Expand Up @@ -8588,7 +8607,9 @@
06E4E7C5197F1D48005DDE69 /* NoSuchElementExceptionTest.m in Sources */,
061AA0D519FEF2E700457D2B /* TestAnnotation.m in Sources */,
0615D3EE1BD83E7300132067 /* FakeFox01.m in Sources */,
6AF8A3631D0776D900B4CA82 /* RetainedWithTest.m in Sources */,
06574A621A8EB081000A3616 /* package-info.m in Sources */,
6AF8A3621D0776D000B4CA82 /* StringTest.m in Sources */,
0696811A1BA8E3D3007AFADF /* Third.m in Sources */,
0615D3E21BD83E7300132067 /* FeatureDescriptorTest.m in Sources */,
06E4E6FA197F1D47005DDE69 /* OldAndroidStreamTokenizerTest.m in Sources */,
Expand Down Expand Up @@ -8663,6 +8684,7 @@
06E4E73A197F1D47005DDE69 /* SmallTests.m in Sources */,
06E4E6E6197F1D47005DDE69 /* FileTest.m in Sources */,
06E4E715197F1D47005DDE69 /* OldObjectInputStreamGetFieldTest.m in Sources */,
6AF8A35D1D07763800B4CA82 /* NativeTimeZoneTest.m in Sources */,
06E4E75C197F1D47005DDE69 /* GZIPOutputStreamTest.m in Sources */,
06E4E6E9197F1D47005DDE69 /* OldAndroidBufferedInputStreamTest.m in Sources */,
06C54AE11A40CA75004B3ACD /* AttributedCharacterIteratorAttributeTest.m in Sources */,
Expand Down

0 comments on commit 3242d25

Please sign in to comment.