Merge pull request #1921 from North101/develop

More DateTime functions and changes
This commit is contained in:
Simon Binder 2022-07-05 22:08:03 +02:00 committed by GitHub
commit 38f343c5e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 232 additions and 25 deletions

View File

@ -39,28 +39,66 @@ extension DateTimeExpressions on Expression<DateTime?> {
Expression<int?> get second => _StrftimeSingleFieldExpression('%S', this);
/// Formats this datetime in the format `year-month-day`.
Expression<String?> get date {
return FunctionCallExpression(
'DATE',
[this, const Constant<String>('unixepoch')],
);
}
Expression<String?> get date => FunctionCallExpression(
'DATE', [this, const DateTimeModifier._unixEpoch()]);
/// Formats this datetime in the format `hour:minute:second`.
Expression<String?> get time {
return FunctionCallExpression(
'TIME',
[this, const Constant<String>('unixepoch')],
);
}
Expression<String?> get time => FunctionCallExpression(
'TIME', [this, const DateTimeModifier._unixEpoch()]);
/// Formats this datetime in the format `year-month-day hour:minute:second`.
Expression<String?> get datetime {
return FunctionCallExpression(
'DATETIME',
[this, const Constant<String>('unixepoch')],
);
}
Expression<String?> get datetime => FunctionCallExpression(
'DATETIME', [this, const DateTimeModifier._unixEpoch()]);
/// Formats this datetime as a unix timestamp - the number of seconds since
/// 1970-01-01 00:00:00 UTC. The unixepoch() always returns an integer, even
/// if the input time-value has millisecond precision.
Expression<int?> get unixepoch => FunctionCallExpression(
'UNIXEPOCH', [this, const DateTimeModifier._unixEpoch()]);
/// Formats this datetime in the Julian day format - a fractional number of
/// days since noon in Greenwich on November 24, 4714 B.C.
Expression<double?> get julianday => FunctionCallExpression(
'JULIANDAY', [this, const DateTimeModifier._unixEpoch()]);
/// Formats this datetime according to the format string specified as the
/// first argument. The format string supports the most common substitutions
/// found in the strftime() function from the standard C library plus two new
/// substitutions, %f and %J. The following is a complete list of valid
/// strftime() substitutions:
/// * %d day of month: 00
/// * %f fractional seconds: SS.SSS
/// * %H hour: 00-24
/// * %j day of year: 001-366
/// * %J Julian day number (fractional)
/// * %m month: 01-12
/// * %M minute: 00-59
/// * %s seconds since 1970-01-01
/// * %S seconds: 00-59
/// * %w day of week 0-6 with Sunday==0
/// * %W week of year: 00-53
/// * %Y year: 0000-9999
/// * %% %
Expression<String?> strftime(String format) => FunctionCallExpression(
'STRFTIME',
[Constant<String>(format), this, const DateTimeModifier._unixEpoch()]);
/// Apply a modifier that alters the date and/or time.
///
/// See the factories on [DateTimeModifier] for a list of modifiers that can
/// be used with this method.
Expression<DateTime?> modify(DateTimeModifier modifier) =>
FunctionCallExpression(
'unixepoch', [this, const DateTimeModifier._unixEpoch(), modifier]);
/// Applies modifiers that alters the date and/or time.
///
/// The [modifiers] are applied in sequence from left to right.
/// For a list of modifiers and how they behave, see the docs on
/// [DateTimeModifier] factories.
Expression<DateTime?> modifyAll(Iterable<DateTimeModifier> modifiers) =>
FunctionCallExpression('unixepoch',
[this, const DateTimeModifier._unixEpoch(), ...modifiers]);
/// Returns an expression containing the amount of seconds from the unix
/// epoch (January 1st, 1970) to `this` datetime expression. The datetime is
@ -69,13 +107,23 @@ extension DateTimeExpressions on Expression<DateTime?> {
// anything when converting
Expression<int> get secondsSinceEpoch => dartCast();
/// Adds [duration] from this date.
/// Adds a [duration] to this date.
///
/// Note that the curation is added as a value in seconds. Thus, adding a
/// `Duration(days: 1)` will not necessary yield the same time tomorrow in all
/// cases (due to daylight saving time switches).
/// To change the value in terms of calendar units, see [modify].
Expression<DateTime> operator +(Duration duration) {
return _BaseInfixOperator(this, '+', Variable<int>(duration.inSeconds),
precedence: Precedence.plusMinus);
}
/// Subtracts [duration] from this date.
///
/// Note that the curation is subtracted as a value in seconds. Thus,
/// subtracting a `Duration(days: 1)` will not necessary yield the same time
/// yesterday in all cases (due to daylight saving time switches). To change
/// the value in terms of calendar units, see [modify].
Expression<DateTime> operator -(Duration duration) {
return _BaseInfixOperator(this, '-', Variable<int>(duration.inSeconds),
precedence: Precedence.plusMinus);
@ -107,3 +155,110 @@ class _StrftimeSingleFieldExpression extends Expression<int?> {
other.date == date;
}
}
/// DateTime modifier constants.
///
/// These modifiers are used on [DateTimeExpressions.modify] and
/// [DateTimeExpressions.modifyAll] to apply transformations on date time
/// values.
///
/// For instance, [DateTimeModifier.days] can be used to add or subtract
/// calendar days from a date time value. Note that this is different from
/// just subtracting a duration with [DateTimeExpressions.+], which only adds a
/// duration as seconds without respecting calendar units.
///
/// For another explanation of modifiers, see the [sqlite3 docs].
///
/// [sqlite3 docs]: https://sqlite.org/lang_datefunc.html#modifiers
class DateTimeModifier extends Constant<String> {
const DateTimeModifier._(super.value);
/// Adds or subtracts [days] calendar days from the date time value.
const DateTimeModifier.days(int days) : this._('$days days');
/// Adds or subtracts [hours] hours from this date time value.
const DateTimeModifier.hours(int hours) : this._('$hours hours');
/// Adds or subtracts [minutes] minutes from this date time value.
const DateTimeModifier.minutes(int minutes) : this._('$minutes minutes');
/// Adds or subtracts [seconds] seconds from this date time value.
///
/// Note that drift assumes date time values to be encoded as unix timestamps
/// (with second accuracy) in the database. So adding seconds with a
/// fractional value may not always be preserved in a chain of computation.
const DateTimeModifier.seconds(num seconds) : this._('$seconds seconds');
/// Adds or subtracts [months] months from this date time value.
///
/// Note that this works by rendering the original date into the `YYYY-MM-DD`
/// format, adding the [months] value to the `MM` field and normalizing the
/// result. Thus, for example, the date 2001-03-31 modified by '+1 month'
/// nitially yields 2001-04-31, but April only has 30 days so the date is
/// normalized to 2001-05-01.
const DateTimeModifier.months(int months) : this._('$months months');
/// Adds or subtracts [years] years from this date time value.
///
/// Similar to the transformation on [DateTimeModifier.months], it may not
/// always be possible to keep the day and month field the same for this
/// transformation. For instance, if the original date is February 29 of a
/// leapyear and one year is added, the result will be in March 1 of the next
/// year as there is no February 29.
const DateTimeModifier.years(int years) : this._('$years years');
/// The "start of day" modifier shifts the date backwards to the beginning of
/// the day.
const DateTimeModifier.startOfDay() : this._('start of day');
/// The "start of month" modifier shifts the date backwards to the beginning
/// of the month.
const DateTimeModifier.startOfMonth() : this._('start of month');
/// The "start of year" modifier shifts the date backwards to the beginning of
/// the year.
const DateTimeModifier.startOfYear() : this._('start of year');
/// The "weekday" modifier shifts the date forward to the next date where the
/// weekday is the [weekday] provided here.
///
/// If the source date is on the desired weekday, no transformation happens.
DateTimeModifier.weekday(DateTimeWeekday weekday)
: this._('weekday ${weekday.index}');
const DateTimeModifier._unixEpoch() : this._('unixepoch');
/// Move a date time that is in UTC to the local time zone.
///
/// See also: [DateTime.toLocal].
const DateTimeModifier.localTime() : this._('localtime');
/// Move a date time that is in the local time zone back to UTC.
///
/// See also: [DateTime.toLocal].
const DateTimeModifier.utc() : this._('utc');
}
/// Weekday offset to be used with [DateTimeModifier.weekday]
enum DateTimeWeekday {
/// Sunday (+0 on [DateTimeModifier.weekday])
sunday,
/// Monday (+1 on [DateTimeModifier.weekday])
monday,
/// Tueday (+2 on [DateTimeModifier.weekday])
tuesday,
/// Wednesday (+3 on [DateTimeModifier.weekday])
wednesday,
/// Thursday (+4 on [DateTimeModifier.weekday])
thursday,
/// Friday (+5 on [DateTimeModifier.weekday])
friday,
/// Saturday (+6 on [DateTimeModifier.weekday])
saturday
}

View File

@ -38,18 +38,70 @@ void main() {
});
test('extracting values from datetime', () {
final expr = Variable.withDateTime(DateTime.utc(2020, 09, 04, 8, 55));
final expr = Variable.withDateTime(DateTime.utc(2020, 09, 03, 23, 55));
expect(eval(expr.year), completion(2020));
expect(eval(expr.month), completion(9));
expect(eval(expr.day), completion(4));
expect(eval(expr.hour), completion(8));
expect(eval(expr.day), completion(3));
expect(eval(expr.hour), completion(23));
expect(eval(expr.minute), completion(55));
expect(eval(expr.second), completion(0));
expect(eval(expr.date), completion('2020-09-04'));
expect(eval(expr.time), completion('08:55:00'));
expect(eval(expr.datetime), completion('2020-09-04 08:55:00'));
expect(eval(expr.date), completion('2020-09-03'));
expect(eval(expr.modify(const DateTimeModifier.days(3)).date),
completion('2020-09-06'));
expect(eval(expr.time), completion('23:55:00'));
expect(eval(expr.datetime), completion('2020-09-03 23:55:00'));
expect(eval(expr.julianday), completion(2459096.496527778));
expect(eval(expr.unixepoch), completion(1599177300));
expect(eval(expr.strftime('%Y-%m-%d %H:%M:%S')),
completion('2020-09-03 23:55:00'));
});
test('date time modifiers', () {
final expr = Variable.withDateTime(DateTime.utc(2022, 07, 05));
expect(eval(expr.modify(const DateTimeModifier.days(2))),
completion(DateTime.utc(2022, 07, 07).toLocal()));
expect(eval(expr.modify(const DateTimeModifier.months(-2))),
completion(DateTime.utc(2022, 05, 05).toLocal()));
expect(eval(expr.modify(const DateTimeModifier.years(1))),
completion(DateTime.utc(2023, 07, 05).toLocal()));
expect(eval(expr.modify(const DateTimeModifier.hours(12))),
completion(DateTime.utc(2022, 07, 05, 12).toLocal()));
expect(eval(expr.modify(const DateTimeModifier.minutes(30))),
completion(DateTime.utc(2022, 07, 05, 0, 30).toLocal()));
expect(eval(expr.modify(const DateTimeModifier.seconds(30))),
completion(DateTime.utc(2022, 07, 05, 0, 0, 30).toLocal()));
expect(eval(expr.modify(const DateTimeModifier.startOfDay())),
completion(DateTime.utc(2022, 07, 05).toLocal()));
expect(eval(expr.modify(const DateTimeModifier.startOfMonth())),
completion(DateTime.utc(2022, 07, 01).toLocal()));
expect(eval(expr.modify(const DateTimeModifier.startOfYear())),
completion(DateTime.utc(2022, 01, 01).toLocal()));
// The original expression is a Tuesday
expect(eval(expr.modify(DateTimeModifier.weekday(DateTimeWeekday.tuesday))),
completion(DateTime.utc(2022, 07, 05).toLocal()));
expect(
eval(expr.modify(DateTimeModifier.weekday(DateTimeWeekday.saturday))),
completion(DateTime.utc(2022, 07, 09).toLocal()),
);
// drift interprets date time values as timestamps, so going to UTC means
// subtracting the UTC offset in SQL. Interpreting that timestamp in Dart
// will effectively add it back, so we have the same value bit without the
// UTC flag in Dart.
expect(eval(expr.modify(const DateTimeModifier.utc())),
completion(DateTime(2022, 07, 05)));
// And vice-versa (note that original expr is in UTC, this one isn't)
expect(
eval(Variable.withDateTime(DateTime(2022, 07, 05))
.modify(const DateTimeModifier.localTime())),
completion(DateTime.utc(2022, 07, 05).toLocal()));
});
test('rowid', () {