Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Calendar class #111

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open

Add Calendar class #111

wants to merge 6 commits into from

Conversation

j-troc
Copy link

@j-troc j-troc commented Mar 21, 2019

This change is Reviewable

@ermshiperete
Copy link
Member

Just a quick question before I dig deeper into the code review:

What benefits does the ICU calendar class give over just using .NET's Calendar class?

@ermshiperete
Copy link
Member

The PR fails two tests on Linux in the CI build:

1) Failed : Icu.Tests.CalendarTests.GetTimeZoneTest
  Expected string length 30 but was 13. Strings differ at index 0.
  Expected: "Central European Standard Time"
  But was:  "Europe/Zagreb"
  -----------^

2) error : Icu.Tests.CalendarTests.SetTimeZone2Test [/home/jenkins/workspace/icu-dotnet_PR-111/build/icu-dotnet.proj]
System.TimeZoneNotFoundException : The time zone ID 'Romance Standard Time' was not found on the local computer.
  ----> System.IO.FileNotFoundException : Could not find file '/usr/share/zoneinfo/Romance Standard Time'.
   at System.TimeZoneInfo.FindSystemTimeZoneById(String id)
   at Icu.Tests.CalendarTests.SetTimeZone2Test() in /home/jenkins/workspace/icu-dotnet_PR-111/source/icu.net.tests/CalendarTests.cs:line 361
--FileNotFoundException
   at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirectory, Func`2 errorRewriter)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String path, OpenFlags flags, Int32 mode)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
   at Internal.IO.File.ReadAllBytes(String path)
   at System.TimeZoneInfo.TryGetTimeZoneFromLocalMachine(String id, TimeZoneInfo& value, Exception& e)

Copy link
Member

@ermshiperete ermshiperete left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 4 of 4 files at r1.
Reviewable status: all files reviewed, 19 unresolved discussions (waiting on @j-troc)


source/icu.net/Calendar/Calendar.cs, line 157 at r1 (raw file):

		protected Locale _locale;

		

nitpick: extra whitespace


source/icu.net/Calendar/Calendar.cs, line 193 at r1 (raw file):

		/// <param name="dateTime">The given date in UTC (GMT) time.</param>
		public void SetTime(DateTime dateTime)
		{

It might make sense to work with dateTime.ToUniversalTime(), then the date can be in any timezone.


source/icu.net/Calendar/Calendar.cs, line 227 at r1 (raw file):

		/// 
		/// The only difference between roll() and add() is that roll() does not change the value of more significant fields
		/// when it reaches the minimum or maximum of its range, whereas add() does.

It might be helpful to add a unit test that demonstrates the difference between the two methods.


source/icu.net/Calendar/Calendar.cs, line 464 at r1 (raw file):

			set
			{
				NativeMethods.ucal_setAttribute(_calendarHandle, UCalendarAttribute.FirstDayOfWeek, value);

Shouldn't this be UCalendarAttribute.MinimalDaysInFirstWeek?!


source/icu.net/Calendar/Calendar.cs, line 507 at r1 (raw file):

			get
			{
				return NativeMethods.ucal_get(_calendarHandle, UCalendarDateFields.Era, out _);

Check error? (same for the other uses of ucal_get)


source/icu.net/Calendar/Calendar.cs, line 646 at r1 (raw file):

		/// Gets or seths whether the hour is before or after noon.
		/// </summary>
		public int AmPm

Wouldn't it be better to use bool as return value here?


source/icu.net/Calendar/Calendar.cs, line 806 at r1 (raw file):

_calendarHandle.Dispose();

This should go in the if (disposing) block. In the case of disposing being false the GC takes care of calling dispose on the calendar handle.


source/icu.net/Calendar/Calendar.cs, line 807 at r1 (raw file):

_disposingValue = true

There seems to be a mismatch between the name and intention of the variable and what it does. If you want to detect redundant calls then you'd have to set it to true right after if (!_disposingValue) and to false before leaving the block. The way it is currently used checks whether or not the object is already disposed - a better name name would then be _isDisposed.


source/icu.net/Calendar/GregorianCalendar.cs, line 40 at r1 (raw file):

		{
			var handle = NativeMethods.ucal_clone(_calendarHandle, out ErrorCode status);
			return new GregorianCalendar(handle);

We should probably copy _locale as well.

Error checking?


source/icu.net/Calendar/GregorianCalendar.cs, line 45 at r1 (raw file):

		public override bool InDaylightTime()
		{
			return NativeMethods.ucal_inDaylightTime(_calendarHandle, out _);

We should probably do some error checking, otherwise returning false can mean not in daylight savings time AND something went wrong which makes it hard to debug in a client app.


source/icu.net/NativeMethods/NativeMethods_Calendar.cs, line 76 at r1 (raw file):

			[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
			internal delegate int ucal_getAttributeDelegate(

Strange that you have it with three parameters here - the docs list only two. Are you sure this is correct?

If this is correct a unit test would be good that proves that this is the correct/working signature (and even if this wrong a unit test would be nice...)


source/icu.net/NativeMethods/NativeMethods_Calendar.cs, line 185 at r1 (raw file):

out string result,

I'd prefer if you change this to IntPtr result and deal with converting to a string in the wrapper method. See for example Normalizer2.GetDecomposition and unorm2_getDecomposition in NativeMethods_Normalize.cs.

This would be more in alignment of the way it's done elsewhere in icu.net.


source/icu.net/NativeMethods/NativeMethods_Calendar.cs, line 237 at r1 (raw file):

		}
		public static void ucal_roll(
		Calendar.SafeCalendarHandle cal,

nitpick: indentation


source/icu.net/NativeMethods/NativeMethods_Calendar.cs, line 322 at r1 (raw file):

out string result

I'd prefer if you change this to IntPtr result and deal with converting to a string in the wrapper method. See for example Normalizer2.GetDecomposition and unorm2_getDecomposition in NativeMethods_Normalize.cs.

This would be more in alignment of the way it's done elsewhere in icu.net.


source/icu.net.tests/CalendarTests.cs, line 18 at r1 (raw file):

		{
			var timezone = new TimeZone("AST");
			var cal = new GregorianCalendar(timezone);

Since Calendar implements IDisposable, all cals have to be wrapped in a using statement:

using (var cal = new GregorianCalendar(timezone))
{
    ....
}

Or make cal a field and dispose it in a TearDown method after each test.


source/icu.net.tests/CalendarTests.cs, line 42 at r1 (raw file):

		{
			var cal = new GregorianCalendar();
			cal.Month = Calendar.UCalendarMonths.September;

For documentation purposes you might want to set cal.DayOfMonth = 4; and check that below.


source/icu.net.tests/CalendarTests.cs, line 58 at r1 (raw file):

			cal2.DayOfMonth = 10;

			Assert.AreEqual(5, cal1.DayOfMonth);

Add Assert.AreEqual(10, cal2.DayOfMonth);


source/icu.net.tests/CalendarTests.cs, line 230 at r1 (raw file):

			cal.Minute = 0;
			cal.Month = Calendar.UCalendarMonths.March;
			cal.DayOfMonth = 13;

Things would be easier to read if you'd sort the properties according to time: Year / Month / DayOfMonth / HourOfDay / Minute. Same for other tests.


source/icu.net.tests/CalendarTests.cs, line 358 at r1 (raw file):

		[Test]

nitpick: extra empty line


source/icu.net.tests/CalendarTests.cs, line 374 at r1 (raw file):

		[Test]
		public void GetTimeZoneTest()

That should be public void GetTimeZoneInfoTest()

@sillsdevgerrit
Copy link

A team member has to approve this pull request on the CI server before it can be built...

Copy link
Member

@ermshiperete ermshiperete left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 of 4 files at r2.
Reviewable status: 1 of 4 files reviewed, 15 unresolved discussions (waiting on @ermshiperete and @j-troc)


source/icu.net.tests/CalendarTests.cs, line 275 at r2 (raw file):

				var val1 = cal.FirstDayOfWeek;

				Assert.AreEqual(Calendar.UCalendarDaysOfWeek.Sunday, val0);

Please write this as separate tests. You can use the TestCase attribute for that. See below.


source/icu.net.tests/CalendarTests.cs, line 294 at r2 (raw file):

				Assert.AreEqual(2, val0);

Please write this as separate tests. You can use the TestCase attribute for that. See below.


source/icu.net.tests/CalendarTests.cs, line 312 at r2 (raw file):

				var val1 = cal.WeekOfYear;

				Assert.AreEqual(2, val0);

Please write this as separate tests. You can use the TestCase attribute for that. See below.


source/icu.net.tests/CalendarTests.cs, line 395 at r2 (raw file):

				var val1 = cal.WeekOfMonth;

				Assert.AreEqual(2, val0);

Please write this as separate tests. You can use the TestCase attribute for that. See below.


source/icu.net.tests/CalendarTests.cs, line 409 at r2 (raw file):

				var era0 = cal.Era;

				Assert.AreEqual(1, era1);

Please write this as separate tests. You can use the TestCase attribute for that. See below.


source/icu.net.tests/CalendarTests.cs, line 445 at r2 (raw file):

				var offset1 = cal.DstOffset;

				Assert.AreEqual(expected0, offset0);

Please write this as separate tests. You can use the TestCase attribute for that:

		[TestCase(Calendar.UCalendarMonths.July,    ExpectedResult = 3600000 /* 60min * 60s * 1000ms */)]
		[TestCase(Calendar.UCalendarMonths.January, ExpectedResult = 0)]
		public int DstOffsetTest(Calendar.UCalendarMonths month)
		{
			var zone = new TimeZone("Europe/Paris");

			using (var cal = new GregorianCalendar(zone))
			{
				cal.Month = month;
				cal.DayOfMonth = 20;

				return cal.DstOffset;
			}
		}

source/icu.net.tests/CalendarTests.cs, line 460 at r2 (raw file):

				var val1 = cal.AmPm;

				Assert.AreEqual(Calendar.UCalendarAMPMs.Am, val0);

Please write this as separate tests. You can use the TestCase attribute for that:

		[TestCase( 3, ExpectedResult = Calendar.UCalendarAMPMs.Am)]
		[TestCase(14, ExpectedResult = Calendar.UCalendarAMPMs.Pm)]
		public Calendar.UCalendarAMPMs AmPmTest(int hourOfDay)
		{
			using (var cal = new GregorianCalendar(new TimeZone("UTC")))
			{
				cal.HourOfDay = hourOfDay;
				return cal.AmPm;
			}
		}

source/icu.net.tests/CalendarTests.cs, line 474 at r2 (raw file):

			var timezone = timezones.FirstOrDefault(tzi => tzi.Id == tzdbId);
			if(timezone==null)

Please create two separate tests, one for Windows and the other for Linux. You can use [Platform(Exclude="win")] and [Platform(Include="win")] to have them run only on one platform.


source/icu.net.tests/CalendarTests.cs, line 501 at r2 (raw file):

				var result = cal.GetTimeZoneInfo();

				Assert.IsTrue(result.Id == winId || result.Id == tzdbId);

Please create two separate tests, one for Windows and the other for Linux. You can use [Platform(Exclude="win")] and [Platform(Include="win")] to have them run only on one platform.

@ermshiperete ermshiperete self-requested a review February 25, 2020 11:29
@sillsdevgerrit
Copy link

A team member has to approve this pull request on the CI server before it can be built...

@sillsdevgerrit
Copy link

A team member has to approve this pull request on the CI server before it can be built...

Copy link
Member

@ermshiperete ermshiperete left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 2 of 4 files at r2, 1 of 1 files at r3.
Reviewable status: all files reviewed, 4 unresolved discussions (waiting on @j-troc)


source/icu.net.tests/CalendarTests.cs, line 473 at r3 (raw file):

		[Test]
		[Platform(Include = "win")]
		public void GetTilmeZoneInfoTestWin()

Typo: should be GetTimeZoneInfoTestWin


source/icu.net.tests/CalendarTests.cs, line 481 at r3 (raw file):

				var result = cal.GetTimeZoneInfo();

				Assert.IsTrue(result.Id == "Central European Standard Time");

Would be better written as Assert.AreEqual("Central European Standard Time", result.Id) or Assert.That(result.Id, Is.EqualTo("Central European Standard Time"))


source/icu.net.tests/CalendarTests.cs, line 497 at r3 (raw file):

				var result = cal.GetTimeZoneInfo();

				Assert.IsTrue(result.Id == tzdbId);

See above

@sillsdevgerrit
Copy link

A team member has to approve this pull request on the CI server before it can be built...

@j-troc
Copy link
Author

j-troc commented Mar 5, 2020

It builds just fine on my machines.

@ermshiperete
Copy link
Member

ermshiperete commented Mar 5, 2020

It builds just fine on my machines.

Did you try on Linux? From the build logs it looks like it gets some memory corruption - possibly the signature for an unmanaged method isn't quite correct yet.

...
  => Icu.Tests.ResourceBundleTests.IsNull
  => Icu.Tests.ResourceBundleTests.Name
  => Icu.Tests.TimeZoneTests.GetCountryTimeZonesTest
  => Icu.Tests.TimeZoneTests.GetDefaultTimeZoneTest
/home/jenkins/workspace/icu-dotnet_PR-111/build/icu-dotnet.proj(96,3): warning : 
/home/jenkins/workspace/icu-dotnet_PR-111/build/icu-dotnet.proj(96,3): warning : =================================================================
/home/jenkins/workspace/icu-dotnet_PR-111/build/icu-dotnet.proj(96,3): warning : 	External Debugger Dump:
/home/jenkins/workspace/icu-dotnet_PR-111/build/icu-dotnet.proj(96,3): warning : ================================================================
/home/jenkins/workspace/icu-dotnet_PR-111/build/icu-dotnet.proj(96,3): warning : mono_gdb_render_native_backtraces not supported on this platform, unable to find gdb or lldb
  
  =================================================================
  	Basic Fault Address Reporting
  =================================================================
  Memory around native instruction pointer (0x7f7f521f4d72):0x7f7f521f4d62  ac 24 38 21 05 d8 1d 31 10 ae 94 1b 18 3e 31 37  .$8!...1.....>17
  0x7f7f521f4d72  00 15 31 2b 00 3a 21 15 14 3a 15 3c 2c 38 21 10  ..1+.:!..:.<,8!.
  0x7f7f521f4d82  2c 20 01 01 da 6f fa 15 14 2d 2f 04 3a 38 21 04  , ...o...-/.:8!.
  0x7f7f521f4d92  00 36 01 d8 1b 26 10 b2 c1 15 d2 c0 21 11 15 3a  .6...&......!..:
  
  =================================================================
  	Managed Stacktrace:
  =================================================================
  	  at <unknown> <0xffffffff>
  	  at System.Object:wrapper_native_0x7f7f521f4d70 <0x00096>
  	  at Icu.NativeMethods:ucal_getDefaultTimeZone <0x000c0>
  	  at <>c:<GetDefault>b__9_0 <0x0003f>
  	  at Icu.NativeMethods:GetString <0x0007d>
  	  at Icu.NativeMethods:GetUnicodeString <0x00033>
  	  at Icu.TimeZone:GetDefault <0x00103>
  	  at Icu.Tests.TimeZoneTests:GetDefaultTimeZoneTest <0x00057>
  	  at System.Object:runtime_invoke_void__this__ <0x00085>
  	  at <unknown> <0xffffffff>
  	  at System.Reflection.RuntimeMethodInfo:InternalInvoke <0x000d0>
  	  at System.Reflection.RuntimeMethodInfo:Invoke <0x00142>
  	  at System.Reflection.MethodBase:Invoke <0x00045>
  	  at NUnit.Framework.Internal.Reflect:InvokeMethod <0x00092>
  	  at NUnit.Framework.Internal.MethodWrapper:Invoke <0x0003b>
  	  at NUnit.Framework.Internal.Commands.TestMethodCommand:InvokeTestMethod <0x00066>
  	  at NUnit.Framework.Internal.Commands.TestMethodCommand:RunTestMethod <0x00177>
  	  at NUnit.Framework.Internal.Commands.TestMethodCommand:Execute <0x00033>
  	  at NUnit.Framework.Internal.Execution.SimpleWorkItem:PerformWork <0x00057>
  	  at NUnit.Framework.Internal.Execution.WorkItem:RunOnCurrentThread <0x00118>
  	  at NUnit.Framework.Internal.Execution.WorkItem:Execute <0x001f3>
  	  at NUnit.Framework.Internal.Execution.ParallelWorkItemDispatcher:Dispatch <0x0012f>
  	  at NUnit.Framework.Internal.Execution.ParallelWorkItemDispatcher:Dispatch <0x0003f>
  	  at NUnit.Framework.Internal.Execution.CompositeWorkItem:RunChildren <0x002f5>
  	  at NUnit.Framework.Internal.Execution.CompositeWorkItem:PerformWork <0x0017f>
  	  at NUnit.Framework.Internal.Execution.WorkItem:RunOnCurrentThread <0x00118>
  	  at NUnit.Framework.Internal.Execution.WorkItem:Execute <0x001f3>
  	  at NUnit.Framework.Internal.Execution.TestWorker:TestWorkerThreadProc <0x001fa>
  	  at System.Threading.ThreadHelper:ThreadStart_Context <0x000a5>
  	  at System.Threading.ExecutionContext:RunInternal <0x0021d>
  	  at System.Threading.ExecutionContext:Run <0x00052>
  	  at System.Threading.ExecutionContext:Run <0x00072>
  	  at System.Threading.ThreadHelper:ThreadStart <0x00052>
  	  at System.Object:runtime_invoke_void__this__ <0x00085>
  =================================================================
  
  Errors, Failures and Warnings
  
  1) Error : 
...

(Note that GetDefaultTimeZoneTest isn't necessarily the test that's causing the problems, although in this case it looks like it might be the problem.)

@sillsdev sillsdev deleted a comment from sillsdevgerrit Mar 16, 2020
@sillsdev sillsdev deleted a comment from sillsdevgerrit Mar 16, 2020
@sillsdevgerrit
Copy link

A team member has to approve this pull request on the CI server before it can be built...

1 similar comment
@sillsdevgerrit
Copy link

A team member has to approve this pull request on the CI server before it can be built...

@github-actions
Copy link

Test Results

0 files   -        4  0 suites   - 320   0s ⏱️ -9s
0 tests  -    431  0 ✔️  -    423  0 💤  -   8  0 ±0 
0 runs   - 1 734  0 ✔️  - 1 675  0 💤  - 59  0 ±0 

Results for commit 691f58e. ± Comparison against base commit 1646551.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants