Home > Software design >  Time difference in Iso8601 formate
Time difference in Iso8601 formate

Time:09-01

I am trying to calculate the difference between two times and I need to send data to data base in this formate "P3Y6M4DT12H30M17S" I am using .difference method and get difference.

Code:

 final birthday = DateTime(1977, 5, 22);
 final birthday1 = DateTime(1967, 10, 12);
 final difference = birthday.difference(birthday1);
 print(difference.toIso8601String()); //error line

Problem:

The method 'toIso8601String' isn't defined for the type 'Duration'.

How I can save time difference in Iso8601String.

CodePudding user response:

ISO-8601 duration strings aren't something to be taken lightly for the reasons I stated in my comment. However, there's the simple version and the "correct" version.

Simple version

Strictly speaking, the ISO-8601 standard doesn't require that it's components include the "correct" number in each value position, so you could just as well use arbitrarily large numbers for the day value and call it good. However, you will be at the mercy of whatever system you send these strings to as far as whether it will accept them. But if you are dealing in durations that rarely go for longer than a day, or if you're using a context that doesn't care about the ability to deconstruct the duration (such as a cron pattern), this approach should be sufficient.

void main() {
  final birthday = DateTime(1977, 5, 22);
  final birthday1 = DateTime(1967, 10, 12);
  final difference = birthday.difference(birthday1);
  print(difference.toIso8601String()); // P3510DT0H0M0S
}

extension IsoDuration on Duration {
  static const _hoursInDays = 24;
  static const _minutesInHours = 60;
  static const _secondsInMinutes = 60;
  static const _millisInSeconds = 1000;
  
  String toIso8601String() {
    final days = this.inDays.toInt();
    final hours = (this.inHours % _hoursInDays).toInt();
    final minutes = (this.inMinutes % _minutesInHours).toInt();
    final seconds = (this.inSeconds % _secondsInMinutes);
    return 'P${days}DT${hours}H${minutes}M${seconds}S';
  }
}

"Correct" Version

Again, duration strings in practice aren't so easy. You need to take into account like number of days in the months spanned by the duration, leap years, leap seconds, time zones, and just random things that affect otherwise normal dates. (There's a reason big complex libraries like moment and luxon exist in the JavaScript world.)

Instead of a regular duration string, the recommendation is to do an interval string which is comprised of both the timestamp for the starting time and the duration stamp. That way something like P3M can make sense if it's given in the context of 2020-01-05T12:00:00/P3M. (The three months in that case can be figured out to mean 91 days since it involves January, February, and March during a leap year.)

There isn't really a "simple" solution for this. The brute force method would be to manually count the years between the start and end time followed by the months, and then use the normal duration difference for the remainder. (Of course, this is assuming that Dart's DateTime class takes all the aforementioned problems into account.)

void main() {
  final birthday = DateTime(1977, 5, 22);
  final birthday1 = DateTime(1967, 10, 12);
  final interval = Interval.fromDates(birthday, birthday1);
  print(interval.toIso8601String()); // 1967-10-12T00:00:00.000/P9Y7M10DT0H0M0S
}

class Interval {
  final DateTime start;
  final int years;
  final int months;
  final int days;
  final int hours;
  final int minutes;
  final double seconds;
  
  const Interval(
    this.start,
    this.years,
    this.months,
    this.days,
    this.hours,
    this.minutes,
    this.seconds,
  );
  
  const Interval.zero(DateTime start) : this(start, 0, 0, 0, 0, 0, 0);
  
  factory Interval.fromDates(DateTime start, DateTime end) {
    if (start.isAtSameMomentAs(end)) return Interval.zero(start);
    if (start.isAfter(end)) {
      final temp = start;
      start = end;
      end = temp;
    }
    
    int years = 0;
    int months = 0;
    var marker = start;
    
    while (_addYear(marker).isBefore(end)) {
      marker = _addYear(marker);
      years  ;
    }
    
    while (_addMonth(marker).isBefore(end)) {
      marker = _addMonth(marker);
      months  ;
    }
    
    final diff = marker.difference(end).abs();
    final days = diff.inDays;
    final hours = diff.inHours % _hoursInDays;
    final minutes = diff.inMinutes % _minutesInHours;
    final seconds = (diff.inMicroseconds % _microsInSeconds) / 1000000;
    
    return Interval(start, years, months, days, hours, minutes, seconds);
  }
  
  static const _hoursInDays = 24;
  static const _minutesInHours = 60;
  static const _secondsInMinutes = 60;
  static const _microsInSeconds = 1000000;
  
  static DateTime _addYear(DateTime dt) {
    return DateTime(dt.year   1, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.millisecond, dt.microsecond);
  }
  
  static DateTime _addMonth(DateTime dt) {
    return DateTime(dt.year, dt.month   1, dt.day, dt.hour, dt.minute, dt.second, dt.millisecond, dt.microsecond);
  }
  
  String toIso8601String() {
    return '${start.toIso8601String()}/P${years}Y${months}M${days}DT${hours}H${minutes}M${seconds}S';
  }
}

A more elegant solution would likely involve a moment-like library in its own right.

  • Related