case 4697 - relative date filters use the user's time zone
Token date ranges (*today*, *lastmonth* ...) are built from the user's local time in clientTimeStamp but were converted to UTC with ToUniversalTime(), the server's time zone. They are now converted with clientTimeStamp.Offset. Lists and dashboard widgets share this builder. Deferred: daylight saving edge and dashboard grouping by UTC day (both need the client's time zone name). Changelog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -416,20 +416,20 @@ namespace AyaNova.DataList
|
||||
dtAfter = ClientToday.AddDays(-1);
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
dtBefore = ClientToday;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*today*":
|
||||
//Between yesterday at midnight and tommorow at midnight
|
||||
dtAfter = ClientToday.AddSeconds(-1);
|
||||
dtBefore = ClientToday.AddDays(1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
case "*tomorrow*":
|
||||
//Between Tonight at midnight and day after tommorow at midnight
|
||||
dtAfter = ClientToday.AddDays(1).AddSeconds(-1);
|
||||
dtBefore = ClientToday.AddDays(2);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
case "*lastweek*":
|
||||
//Between two Sundays ago at midnight and last sunday at midnight
|
||||
@@ -442,7 +442,7 @@ namespace AyaNova.DataList
|
||||
//go to very start of eighth dayahead
|
||||
dtBefore = dtAfter.AddDays(8);
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
case "*thisweek*":
|
||||
//Between Sunday at midnight and Next sunday at midnight
|
||||
@@ -467,7 +467,7 @@ namespace AyaNova.DataList
|
||||
dtBefore = dtBefore.AddDays(1);
|
||||
}
|
||||
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
case "*nextweek*":
|
||||
//Between Next Sunday at midnight and Next Next sunday at midnight
|
||||
@@ -486,7 +486,7 @@ namespace AyaNova.DataList
|
||||
dtBefore = dtAfter.AddDays(7);
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
case "*lastmonth*":
|
||||
//start with the first day of this month
|
||||
@@ -500,7 +500,7 @@ namespace AyaNova.DataList
|
||||
//case 1155
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
case "*thismonth*":
|
||||
//start with the first day of this month
|
||||
@@ -512,7 +512,7 @@ namespace AyaNova.DataList
|
||||
//case 1155
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*nextmonth*":
|
||||
@@ -526,7 +526,7 @@ namespace AyaNova.DataList
|
||||
|
||||
//case 1155
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*14daywindow*":
|
||||
@@ -540,21 +540,21 @@ namespace AyaNova.DataList
|
||||
//case 1155
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*past*":
|
||||
//Forever up to Now
|
||||
dtAfter = new DateTime(1753, 1, 2);//this was for sql server but even then was probably outdated good enough though for our purposes
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*future*":
|
||||
//From Now to forever (999 years from now)
|
||||
dtAfter = ClientNow;
|
||||
dtBefore = dtAfter.AddYears(999);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*lastyear*":
|
||||
@@ -562,7 +562,7 @@ namespace AyaNova.DataList
|
||||
dtAfter = new DateTime(ClientNow.AddYears(-1).Year, 1, 1, 0, 0, 00).AddSeconds(-1);
|
||||
//To zero hour January 1 this year
|
||||
dtBefore = new DateTime(ClientNow.Year, 1, 1, 0, 0, 00);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*thisyear*":
|
||||
@@ -570,7 +570,7 @@ namespace AyaNova.DataList
|
||||
dtAfter = new DateTime(ClientNow.Year, 1, 1).AddSeconds(-1);
|
||||
//To zero hour Jan 1 next year
|
||||
dtBefore = new DateTime(ClientNow.AddYears(1).Year, 1, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*last3months*":
|
||||
@@ -578,7 +578,7 @@ namespace AyaNova.DataList
|
||||
dtAfter = ClientNow.AddMonths(-3).AddSeconds(-1);
|
||||
//To Now
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*last6months*":
|
||||
@@ -586,7 +586,7 @@ namespace AyaNova.DataList
|
||||
dtAfter = ClientNow.AddMonths(-6).AddSeconds(-1);
|
||||
//To Now
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*pastyear*": //within the prior 365 days before today
|
||||
@@ -594,7 +594,7 @@ namespace AyaNova.DataList
|
||||
dtAfter = ClientNow.AddDays(-365).AddSeconds(-1);
|
||||
//To Now
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*past90days*":
|
||||
@@ -602,21 +602,21 @@ namespace AyaNova.DataList
|
||||
dtAfter = ClientNow.AddDays(-90).AddSeconds(-1);
|
||||
//To Now
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
case "*past30days*":
|
||||
//From Now minus 30 days
|
||||
dtAfter = ClientNow.AddDays(-30).AddSeconds(-1);
|
||||
//To Now
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
case "*past7days*":
|
||||
//From Now minus 7 days
|
||||
dtAfter = ClientNow.AddDays(-7).AddSeconds(-1);
|
||||
//To Now
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*past24hours*":
|
||||
@@ -624,7 +624,7 @@ namespace AyaNova.DataList
|
||||
dtAfter = ClientNow.AddHours(-24).AddSeconds(-1);
|
||||
//To Now
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*past6hours*":
|
||||
@@ -632,7 +632,7 @@ namespace AyaNova.DataList
|
||||
dtAfter = ClientNow.AddHours(-6).AddSeconds(-1);
|
||||
//To Now
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*january*":
|
||||
@@ -640,67 +640,67 @@ namespace AyaNova.DataList
|
||||
dtAfter = new DateTime(ClientNow.Year, 1, 1).AddSeconds(-1);
|
||||
//To zero hour feb 1 this year
|
||||
dtBefore = new DateTime(ClientNow.Year, 2, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*february*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 2, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 3, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*march*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 3, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 4, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*april*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 4, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 5, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*may*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 5, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 6, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*june*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 6, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 7, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*july*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 7, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 8, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*august*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 8, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 9, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*september*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 9, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 10, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*october*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 10, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 11, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*november*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 11, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 12, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*december*":
|
||||
@@ -708,7 +708,7 @@ namespace AyaNova.DataList
|
||||
dtAfter = new DateTime(ClientNow.Year, 12, 1).AddSeconds(-1);
|
||||
//To zero hour Jan 1 next year
|
||||
dtBefore = new DateTime(ClientNow.AddYears(1).Year, 1, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
case "*lastyearlastmonth*":
|
||||
//start with the first day of this month
|
||||
@@ -718,7 +718,7 @@ namespace AyaNova.DataList
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.AddMonths(1);
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*lastyearthismonth*":
|
||||
@@ -729,7 +729,7 @@ namespace AyaNova.DataList
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.AddMonths(1);
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
case "*lastyearnextmonth*":
|
||||
@@ -742,7 +742,7 @@ namespace AyaNova.DataList
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.AddMonths(1);
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore, clientTimeStamp.Offset);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -1075,19 +1075,27 @@ namespace AyaNova.DataList
|
||||
//This is only used by the token date query code above and that code does NOT convert to UTC time to match the DB so this function will handle that
|
||||
//The other non tokenized date criteria builders are all working with dates that come from the client in UTC already and they don't use this method
|
||||
//so nothing required there
|
||||
private static void BuildSQLBetweenTwoTokenDerivedDates(string sColumn, StringBuilder sb, DateTime dtAfter, DateTime dtBefore)
|
||||
//case 4697: the token dates are the user's local times (from clientTimeStamp), so they are converted to UTC with the
|
||||
//user's offset; ToUniversalTime() used the server's time zone. A range that crosses a daylight saving change can be
|
||||
//an hour off at its far end, as the client sends only its current offset
|
||||
private static void BuildSQLBetweenTwoTokenDerivedDates(string sColumn, StringBuilder sb, DateTime dtAfter, DateTime dtBefore, TimeSpan clientOffset)
|
||||
{
|
||||
|
||||
sb.Append(">'");
|
||||
sb.Append(PostgresDateFormat(MaxSeconds(dtAfter.ToUniversalTime())));
|
||||
sb.Append(PostgresDateFormat(MaxSeconds(ClientLocalToUtc(dtAfter, clientOffset))));
|
||||
sb.Append("' AND ");
|
||||
sb.Append(sColumn);
|
||||
sb.Append(" ");
|
||||
sb.Append("<'");
|
||||
sb.Append(PostgresDateFormat(ZeroSeconds(dtBefore.ToUniversalTime())));
|
||||
sb.Append(PostgresDateFormat(ZeroSeconds(ClientLocalToUtc(dtBefore, clientOffset))));
|
||||
sb.Append("'");
|
||||
}
|
||||
|
||||
private static DateTime ClientLocalToUtc(DateTime clientLocal, TimeSpan clientOffset)
|
||||
{
|
||||
return DateTime.SpecifyKind(clientLocal - clientOffset, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
|
||||
private static DateTime ZeroSeconds(DateTime d)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user