// Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

part of "dart:core";

/// A span of time, such as 27 days, 4 hours, 12 minutes, and 3 seconds.
///
/// A `Duration` represents a difference from one point in time to another. The
/// duration may be "negative" if the difference is from a later time to an
/// earlier.
///
/// Durations are context independent. For example, a duration of 2 days is
/// always 48 hours, even when it is added to a `DateTime` just when the
/// time zone is about to make a daylight-savings switch. (See [DateTime.add]).
///
/// Despite the same name, a `Duration` object does not implement "Durations"
/// as specified by ISO 8601. In particular, a duration object does not keep
/// track of the individually provided members (such as "days" or "hours"), but
/// only uses these arguments to compute the length of the corresponding time
/// interval.
///
/// To create a new `Duration` object, use this class's single constructor
/// giving the appropriate arguments:
/// ```dart
/// const fastestMarathon = Duration(hours: 2, minutes: 3, seconds: 2);
/// ```
/// The [Duration] represents a single number of microseconds,
/// which is the sum of all the individual arguments to the constructor.
///
/// Properties can access that single number in different ways.
/// For example the [inMinutes] gives the number of whole minutes
/// in the total duration, which includes the minutes that were provided
/// as "hours" to the constructor, and can be larger than 59.
///
/// ```dart
/// const fastestMarathon = Duration(hours: 2, minutes: 0, seconds: 35);
/// print(fastestMarathon.inDays); // 0
/// print(fastestMarathon.inHours); // 2
/// print(fastestMarathon.inMinutes); // 120
/// print(fastestMarathon.inSeconds); // 7235
/// print(fastestMarathon.inMilliseconds); // 7235000
/// ```
/// The duration can be negative, in which case
/// all the properties derived from the duration are also non-positive.
/// ```dart
/// const overDayAgo = Duration(days: -1, hours: -10);
/// print(overDayAgo.inDays); // -1
/// print(overDayAgo.inHours); // -34
/// print(overDayAgo.inMinutes); // -2040
/// ```
/// Use one of the properties, such as [inDays],
/// to retrieve the integer value of the `Duration` in the specified time unit.
/// Note that the returned value is rounded down.
/// For example,
/// ```dart
/// const aLongWeekend = Duration(hours: 88);
/// print(aLongWeekend.inDays); // 3
/// ```
/// This class provides a collection of arithmetic
/// and comparison operators,
/// plus a set of constants useful for converting time units.
/// ```dart
/// const firstHalf = Duration(minutes: 45); // 00:45:00.000000
/// const secondHalf = Duration(minutes: 45); // 00:45:00.000000
/// const overTime = Duration(minutes: 30); // 00:30:00.000000
/// final maxGameTime = firstHalf + secondHalf + overTime;
/// print(maxGameTime.inMinutes); // 120
///
/// // The duration of the firstHalf and secondHalf is the same, returns 0.
/// var result = firstHalf.compareTo(secondHalf);
/// print(result); // 0
///
/// // Duration of overTime is shorter than firstHalf, returns < 0.
/// result = overTime.compareTo(firstHalf);
/// print(result); // < 0
///
/// // Duration of secondHalf is longer than overTime, returns > 0.
/// result = secondHalf.compareTo(overTime);
/// print(result); // > 0
/// ```
///
/// **See also:**
/// * [DateTime] to represent a point in time.
/// * [Stopwatch] to measure time-spans.
class Duration implements Comparable<Duration> {
  /// The number of microseconds per millisecond.
  static const int microsecondsPerMillisecond = 1000;

  /// The number of milliseconds per second.
  static const int millisecondsPerSecond = 1000;

  /// The number of seconds per minute.
  ///
  /// Notice that some minutes of official clock time might
  /// differ in length because of leap seconds.
  /// The [Duration] and [DateTime] classes ignore leap seconds
  /// and consider all minutes to have 60 seconds.
  static const int secondsPerMinute = 60;

  /// The number of minutes per hour.
  static const int minutesPerHour = 60;

  /// The number of hours per day.
  ///
  /// Notice that some days may differ in length because
  /// of time zone changes due to daylight saving.
  /// The [Duration] class is time zone agnostic and
  /// considers all days to have 24 hours.
  static const int hoursPerDay = 24;

  /// The number of microseconds per second.
  static const int microsecondsPerSecond =
      microsecondsPerMillisecond * millisecondsPerSecond;

  /// The number of microseconds per minute.
  static const int microsecondsPerMinute =
      microsecondsPerSecond * secondsPerMinute;

  /// The number of microseconds per hour.
  static const int microsecondsPerHour = microsecondsPerMinute * minutesPerHour;

  /// The number of microseconds per day.
  static const int microsecondsPerDay = microsecondsPerHour * hoursPerDay;

  /// The number of milliseconds per minute.
  static const int millisecondsPerMinute =
      millisecondsPerSecond * secondsPerMinute;

  /// The number of milliseconds per hour.
  static const int millisecondsPerHour = millisecondsPerMinute * minutesPerHour;

  /// The number of milliseconds per day.
  static const int millisecondsPerDay = millisecondsPerHour * hoursPerDay;

  /// The number of seconds per hour.
  static const int secondsPerHour = secondsPerMinute * minutesPerHour;

  /// The number of seconds per day.
  static const int secondsPerDay = secondsPerHour * hoursPerDay;

  /// The number of minutes per day.
  static const int minutesPerDay = minutesPerHour * hoursPerDay;

  /// An empty duration, representing zero time.
  static const Duration zero = Duration(seconds: 0);

  /// The total microseconds of this [Duration] object.
  final int _duration;

  /// Creates a new [Duration] object whose value
  /// is the sum of all individual parts.
  ///
  /// Individual parts can be larger than the number of those
  /// parts in the next larger unit.
  /// For example, [hours] can be greater than 23.
  /// If this happens, the value overflows into the next larger
  /// unit, so 26 [hours] is the same as 2 [hours] and
  /// one more [days].
  /// Likewise, values can be negative, in which case they
  /// underflow and subtract from the next larger unit.
  ///
  /// If the total number of microseconds cannot be represented
  /// as an integer value, the number of microseconds might overflow
  /// and be truncated to a smaller number of bits,
  /// or it might lose precision.
  ///
  /// All arguments are 0 by default.
  /// ```dart
  /// const duration = Duration(days: 1, hours: 8, minutes: 56, seconds: 59,
  ///   milliseconds: 30, microseconds: 10);
  /// print(duration); // 32:56:59.030010
  /// ```
  const Duration({
    int days = 0,
    int hours = 0,
    int minutes = 0,
    int seconds = 0,
    int milliseconds = 0,
    int microseconds = 0,
  }) : this._microseconds(
         microseconds +
             microsecondsPerMillisecond * milliseconds +
             microsecondsPerSecond * seconds +
             microsecondsPerMinute * minutes +
             microsecondsPerHour * hours +
             microsecondsPerDay * days,
       );

  // Fast path internal direct constructor to avoids the optional arguments
  // and [_microseconds] recomputation.
  // The `+ 0` prevents -0.0 on the web, if the incoming duration happens to be -0.0.
  const Duration._microseconds(int duration) : _duration = duration + 0;

  /// Adds this Duration and [other] and
  /// returns the sum as a new Duration object.
  Duration operator +(Duration other) {
    return Duration._microseconds(_duration + other._duration);
  }

  /// Subtracts [other] from this Duration and
  /// returns the difference as a new Duration object.
  Duration operator -(Duration other) {
    return Duration._microseconds(_duration - other._duration);
  }

  /// Multiplies this Duration by the given [factor] and returns the result
  /// as a new Duration object.
  ///
  /// Note that when [factor] is a double, and the duration is greater than
  /// 53 bits, precision is lost because of double-precision arithmetic.
  Duration operator *(num factor) {
    return Duration._microseconds((_duration * factor).round());
  }

  /// Divides this Duration by the given [quotient] and returns the truncated
  /// result as a new Duration object.
  ///
  /// The [quotient] must not be `0`.
  Duration operator ~/(int quotient) {
    // By doing the check here instead of relying on "~/" below we get the
    // exception even with dart2js.
    if (quotient == 0) throw IntegerDivisionByZeroException();
    return Duration._microseconds(_duration ~/ quotient);
  }

  /// Whether this [Duration] is shorter than [other].
  bool operator <(Duration other) => this._duration < other._duration;

  /// Whether this [Duration] is longer than [other].
  bool operator >(Duration other) => this._duration > other._duration;

  /// Whether this [Duration] is shorter than or equal to [other].
  bool operator <=(Duration other) => this._duration <= other._duration;

  /// Whether this [Duration] is longer than or equal to [other].
  bool operator >=(Duration other) => this._duration >= other._duration;

  /// The number of entire days spanned by this [Duration].
  ///
  /// For example, a duration of four days and three hours
  /// has four entire days.
  /// ```dart
  /// const duration = Duration(days: 4, hours: 3);
  /// print(duration.inDays); // 4
  /// ```
  int get inDays => _duration ~/ Duration.microsecondsPerDay;

  /// The number of entire hours spanned by this [Duration].
  ///
  /// The returned value can be greater than 23.
  /// For example, a duration of four days and three hours
  /// has 99 entire hours.
  /// ```dart
  /// const duration = Duration(days: 4, hours: 3);
  /// print(duration.inHours); // 99
  /// ```
  int get inHours => _duration ~/ Duration.microsecondsPerHour;

  /// The number of whole minutes spanned by this [Duration].
  ///
  /// The returned value can be greater than 59.
  /// For example, a duration of three hours and 12 minutes
  /// has 192 minutes.
  /// ```dart
  /// const duration = Duration(hours: 3, minutes: 12);
  /// print(duration.inMinutes); // 192
  /// ```
  int get inMinutes => _duration ~/ Duration.microsecondsPerMinute;

  /// The number of whole seconds spanned by this [Duration].
  ///
  /// The returned value can be greater than 59.
  /// For example, a duration of three minutes and 12 seconds
  /// has 192 seconds.
  /// ```dart
  /// const duration = Duration(minutes: 3, seconds: 12);
  /// print(duration.inSeconds); // 192
  /// ```
  int get inSeconds => _duration ~/ Duration.microsecondsPerSecond;

  /// The number of whole milliseconds spanned by this [Duration].
  ///
  /// The returned value can be greater than 999.
  /// For example, a duration of three seconds and 125 milliseconds
  /// has 3125 milliseconds.
  /// ```dart
  /// const duration = Duration(seconds: 3, milliseconds: 125);
  /// print(duration.inMilliseconds); // 3125
  /// ```
  int get inMilliseconds => _duration ~/ Duration.microsecondsPerMillisecond;

  /// The number of whole microseconds spanned by this [Duration].
  ///
  /// The returned value can be greater than 999999.
  /// For example, a duration of three seconds, 125 milliseconds and
  /// 369 microseconds has 3125369 microseconds.
  /// ```dart
  /// const duration = Duration(seconds: 3, milliseconds: 125,
  ///     microseconds: 369);
  /// print(duration.inMicroseconds); // 3125369
  /// ```
  int get inMicroseconds => _duration;

  /// Whether this [Duration] has the same length as [other].
  ///
  /// Durations have the same length if they have the same number
  /// of microseconds, as reported by [inMicroseconds].
  bool operator ==(Object other) =>
      other is Duration && _duration == other.inMicroseconds;

  int get hashCode => _duration.hashCode;

  /// Compares this [Duration] to [other], returning zero if the values are equal.
  ///
  /// Returns a negative integer if this [Duration] is shorter than
  /// [other], or a positive integer if it is longer.
  ///
  /// A negative [Duration] is always considered shorter than a positive one.
  ///
  /// It is always the case that `duration1.compareTo(duration2) < 0` iff
  /// `(someDate + duration1).compareTo(someDate + duration2) < 0`.
  int compareTo(Duration other) => _duration.compareTo(other._duration);

  /// Returns a string representation of this [Duration].
  ///
  /// Returns a string with hours, minutes, seconds, and microseconds, in the
  /// following format: `H:MM:SS.mmmmmm`. For example,
  /// ```dart
  /// var d = const Duration(days: 1, hours: 1, minutes: 33, microseconds: 500);
  /// print(d.toString()); // 25:33:00.000500
  ///
  /// d = const Duration(hours: 1, minutes: 10, microseconds: 500);
  /// print(d.toString()); // 1:10:00.000500
  /// ```
  String toString() {
    var microseconds = inMicroseconds;
    var sign = "";
    var negative = microseconds < 0;

    var hours = microseconds ~/ microsecondsPerHour;
    microseconds = microseconds.remainder(microsecondsPerHour);

    // Correcting for being negative after first division, instead of before,
    // to avoid negating min-int, -(2^31-1), of a native int64.
    if (negative) {
      hours = 0 - hours; // Not using `-hours` to avoid creating -0.0 on web.
      microseconds = 0 - microseconds;
      sign = "-";
    }

    var minutes = microseconds ~/ microsecondsPerMinute;
    microseconds = microseconds.remainder(microsecondsPerMinute);

    var minutesPadding = minutes < 10 ? "0" : "";

    var seconds = microseconds ~/ microsecondsPerSecond;
    microseconds = microseconds.remainder(microsecondsPerSecond);

    var secondsPadding = seconds < 10 ? "0" : "";

    // Padding up to six digits for microseconds.
    var microsecondsText = microseconds.toString().padLeft(6, "0");

    return "$sign$hours:"
        "$minutesPadding$minutes:"
        "$secondsPadding$seconds."
        "$microsecondsText";
  }

  /// Whether this [Duration] is negative.
  ///
  /// A negative [Duration] represents the difference from a later time to an
  /// earlier time.
  bool get isNegative => _duration < 0;

  /// Creates a new [Duration] representing the absolute length of this
  /// [Duration].
  ///
  /// The returned [Duration] has the same length as this one, but is always
  /// positive where possible.
  Duration abs() => Duration._microseconds(_duration.abs());

  /// Creates a new [Duration] with the opposite direction of this [Duration].
  ///
  /// The returned [Duration] has the same length as this one, but will have the
  /// opposite sign (as reported by [isNegative]) as this one where possible.
  // Using subtraction helps dart2js avoid negative zeros.
  Duration operator -() => Duration._microseconds(0 - _duration);
}
