Subtracts dates (including time)

Hello:

I have to subtract to dates than include the time

1 T1 (T)
1 T2 (T)
1 T3 (T)

MOVE EDITED ‘201408250910000’ TO T1 (EM=YYYYMMDDHHIISST)
MOVE EDITED ‘201408261010000’ TO T2 (EM=YYYYMMDDHHIISST)

COMPUTE T3 = T2 - T1

WRITE T3 (EM=YYYYMMDD’-'HHIISST)

The result is: 00000103-0100000

The real diference is 1 day and 1 hour, how can I obtain that?

When you subtract two T format values (or 2 D format values), what you end up with is simply a number (not in T or D format).

When subtracting two T format values, since time is defined as number of tenths of a second since the “point of origin”, you can take this resulting number and divide by 10, taking the remainder as number of 10ths of a second, then divide by 60, taking the remainder as number of seconds, divide by 60 again, taking the remainder as number of minutes, divide by 24, taking the remainder as number of hours, etc…

This will give you a way to come up with the 1 day, 1 hour difference you are looking for.

Can`t disagree LESS with Brian!
Indeed, T2 - T1 gives us exactly 900000 (in tens of second) = 90000 SECONDS; and divided by 3600 = 25 hours: 1 day + 1 hour; so, the difference is 1 DAY and 1 HOUR.
However, I would not recommend to interpret the result as DATE (?)

T format: TIME1 - TIME2 = number of tenths of a second between them

D format: DATE1 - DATE2 = number of days between them.

In a weird sort of way, Fernando’s result even with the T format actually does indicate 1 day, 1 hour as well because the point of origin is 0000-01-02 00:00:00.

Date 0000-01-03 01:00:00 is exactly 1 day, 1 hour after that, but like you said, Nikolay, it is not recommended to use D or T formats to interpret the subtracted result. I would use a numeric variable to assign the difference to, then use arithmetic on the numeric difference to determine that instead.

The calculations are very simple: 3 ASSIGN statements.

DEFINE DATA LOCAL
1 #TFROM (T)       INIT <E'07/05/2014 07:00:00'>
1 #TTHRU (T)       INIT <E'07/07/2014 01:10:01'>
1 #HOURS (T)       (HD='HH:MM:SS')
1 #D (D)
1 #DAYS (N7)       (HD='Days')
END-DEFINE
ASSIGN #HOURS = #TTHRU - #TFROM   /* duration
ASSIGN #D     = #HOURS            /* extract days
ASSIGN #DAYS  = #D                /* D to numeric format
DISPLAY #DAYS
        #HOURS
END
Page     1                                                   08/26/14  12:44:17
 
  Days   HH:MM:SS
-------- --------
 
       1 18:10:01

Added Fernando’s example:

1 #TFROM (T)       INIT <E'08/25/2014 09:10:00'>
1 #TTHRU (T)       INIT <E'08/26/2014 10:10:00'>
Page     1                                                   08/26/14  23:36:14
 
  Days   HH:MM:SS
-------- --------
 
       1 01:00:00

Hi Ralph;

Having never had occasion to extract just a date from a time variable, I had never seen the cute extraction in your program. Thanks.

Too bad I just didn’t store a mental reminder in case I needed it.

Instead, I stared at the code a bit and wondered why the two steps were necessary to get at the number of days.
It seemed to me I should bave been able to pick it up directly by doing EM=DD on #HOURS.

Here is some of the code I played with. I added some redefines to your code and a few extra WRITEs.

DEFINE DATA LOCAL  
1 #TFROM (T)       
1 #TTHRU (T)     
1 #HOURS (T)        (HD='HH:MM:SS')  
1 REDEFINE #HOURS
  2 #TENTHS (B7)
1 #D (D)  
1 REDEFINE #D
  2 #DB (B4)
1 #DAYS (N7)       (HD='Days')  
1 #TIMER (N7)
1 #ALPHA (A2)
END-DEFINE  
/*
MOVE EDITED '20140706070000' to #TFROM (EM=YYYYMMDDHHIISS)
MOVE EDITED '20140709011001' to #TTHRU (EM=YYYYMMDDHHIISS)
/*
ASSIGN #HOURS = #TTHRU - #TFROM   /* duration  
       COMPUTE #TIMER = 2 * 24 * 60 * 60 * 10 + 18 * 60 * 60 * 10 + 10 * 60 * 10 + 10
       WRITE (CD=RE) '=' #HOURS 5x '=' #TENTHS (EM=H(7)) / '=' #HOURS (EM=ddhhiiss) 5X '=' #TIMER /
/*
ASSIGN #D     = #HOURS            /* extract days 
       WRITE '=' #DB (em=h(4))  /         /*  '=' #D   DOES NOT WORK.. DATE OUT OF RANGE
/* 
ASSIGN #DAYS  = #D                /* D to numeric format  
DISPLAY #DAYS  
        #HOURS  #HOURS (em=dd)  /* #hours (em=h(8))   
/*
MOVE EDITED #HOURS (EM=DD) TO #ALPHA
WRITE / '=' #ALPHA           
END  

Page 1 14-08-27 11:21:26

Days HH:MM:SS HH:MM:SS


HH:MM:SS: 18:10:01 #TENTHS: 0000002382010C
HH:MM:SS: 04181001 #TIMER: 2382010

#DB: 0000002C

   2 18:10:01 04

#ALPHA: 04

The DISPLAY, which is from your code is correct (I added a day when entering the “to time”).

In the first WRITE statement, I have computed (long way) #TIMER which is the number of tenths of seconds for the interval. It matches the REDEFINE of #HOURS into #TENTHS which is binary. HOWEVER, on the second line of the first WRITE, note the 04 for DD.

I double checked what was going on by way of the MOVE EDITED to #ALPHA, which shows 04, not 02.

Am I missing something obvious? Do I need more caffeine? (probably)
Might it have something to do with the fact that the “date” inside #HOURS is out of range? seems a possibility. But, I would have expected an error message, not a strange value.

Thanks,

steve

Steve,

If your question is, why is DD showing 04 instead of two, when there is obviously a two day difference between your times, it is because a time variable without the date component initialized is 01/02/0000. The 04 you are seeing is because the date part of the T variable is 01/04/0000, or, two days more than the beginning of time as far as the T variable is concerned. Does this clear up your confusion or am I not understanding what has you confused?

I actually stumbled across the ability to extract the date portion from a T variable by using a D variable while working with a time capture system. A T variable was being stored with the date component when only the time component should have been.

One needs to be careful and understand that once you “extract” the date value, the T variable still contains the date portion. To truly separate the two, after extracting the date to a D variable, you can then subtract the D variable from the time variable to leave only the time component in the T variable. If you use the date and time variables in calculations without doing this, you will get unexpected results.

Typically, I redefine the T variables to P13 and do math that way. T variables do not work as expected when performing multiplication and division on them. I found that out the hard way when trying to calculate a total time when the values available were per piece times and quantities.

Hi Todd;

Actually, my confusion is sort of related to the null date being 01/02/0000, but I think what really annoyed me is what the edit mask did. Also, it has to do with the dual function of a time variable.

Let me try to explain this. If I move an alpha value to a time variable using an EM, I get a binary number which is the number of tenths of seconds from the start of day one, year zero.

define data local
1 #DATE (D)
1 REDEFINE #DATE
2 #DATEB (B4)
1 #DAYS (N8)
END-DEFINE
*
COMPUTE #DAYS = 365 * 2014 + 503 - 20 + 5 + 238
MOVE *DATX TO #DATE
WRITE #DATE (EM=YYYYMMDD) #DATEB #DAYS #DATE (EM=JJJ)
END

Page 1 14-08-27 15:28:59

20140827 0735836C 735836 239

In other words, Natural makes “date sense” out of the binary number.

If you look at my computer run above, the computation of #TIMER matches that #TENTHS. It has two days (in tenths of seconds) plus 18 hours, ten minutes and 1 second in binary.

If this worked the way I thought it would, it would give me something like year 0000 and day 02 and month 01.

I do not see why Natural would add 2 (from a null time) to the actual 2.

In the example just above using today’s date, the day is 27, not 29.

Oh well, I should simply be happy to have found out about the simple way to extract days from a time variable (thanks again Ralph).

BUT, it still bothers me.

Rookies. :slight_smile:

Despite being available for a decade (?), I know few programmers who are aware that D and T format fields can be used for durations.

When you add an integer to a date value or subtract from one, you get a new date, but when you subtract one date from another the result is a duration - a number of days. You need to know in your logic whether the D variable is a date or a duration. Using a date edit mask on a duration makes no sense, as in

MOVE EDITED #HOURS (EM=DD) TO #ALPHA

Subtracting one time value from another has a similar result - a number of days, hours, minutes, and seconds. The T portion of the result result will always be less than 24:00:00 because 24 hours is a day, and the number of days in the duration is placed in the date portion of the T variable. You need to know in your logic whether the T variable is a time or a duration.

I don’t use P13 redefinitions of T variables. I can add hours, minutes and seconds to a T variable via a literal or another T variable.

DEFINE DATA LOCAL
1 #PDT (T)  INIT <*TIMX> (HD='Pacific Time')
1 #NDT (T)               (HD='Newfie Time')
END-DEFINE
COMPUTE #NDT = #PDT + T'03:30:00'     /* 3.5 hours
DISPLAY #PDT (EM=YYYY/MM/DD^HH:II:SS)
        #NDT (EM=YYYY/MM/DD^HH:II:SS)
END
Page     1                                                   08/27/14  13:09:13
 
   Pacific Time         Newfie Time
------------------- -------------------
 
2014/08/27 13:09:13 2014/08/27 16:39:13

I don’t recall in which manual this is documented.

Hi Ralph

Okay, I understand your paragraph:

The problem is that Natural lets me err greatly. This is the heart of what was bothering me about this. Consider:

DEFINE DATA LOCAL  
1 #TFROM (T)       
1 #TTHRU (T) 
1 REDEFINE #TTHRU
  2 #TTHRUB (B7)    
1 #HOURS (T)        (HD='HH:MM:SS')  
1 REDEFINE #HOURS
  2 #TENTHS (B7)
1 #D (D)  
1 REDEFINE #D
  2 #DB (B4)
1 #DAYS (N7)       (HD='Days')  
1 #TIMER (N10)
1 #ALPHA (A2)
END-DEFINE 
/*
MOVE EDITED '20140206070000' to #TFROM (EM=YYYYMMDDHHIISS)
MOVE EDITED '20140905011001' to #TTHRU (EM=YYYYMMDDHHIISS)
WRITE '=' #TTHRUB 
/*
ASSIGN #HOURS = #TTHRU - #TFROM   /* duration 
compute #timer = 210 * 24 * 60 * 60 * 10 + 18 * 60 * 60 * 10 + 10 * 60 * 10 + 10 
       
       WRITE (CD=RE) '=' #HOURS 5x '=' #TENTHS (EM=H(7)) / '=' #HOURS (EM=ddhhiiss)  5x '=' #hours (em=yyyymmddhhiiss) / '=' #TIMER //
/*
ASSIGN #D     = #HOURS            /* extract days 
       WRITE '=' #DB (em=h(4))  /         /*  '=' #D   DOES NOT WORK.. DATE OUT OF RANGE
/* 
ASSIGN #DAYS  = #D                /* D to numeric format  
DISPLAY #DAYS  
        #HOURS  #HOURS (em=dd)  /* #hours (em=h(8))   
/*
MOVE EDITED #HOURS (EM=DD) TO #ALPHA
WRITE / '=' #ALPHA           
END  




PAGE #   1                    DATE:    14-08-28
PROGRAM: TIME04               LIBRARY: INSIDE

Days HH:MM:SS HH:MM:SS


#TTHRUB: 0635770122010C
HH:MM:SS: 18:10:01 #TENTHS: 0000182094010C
HH:MM:SS: 30181001 HH:MM:SS: 00000730181001
#TIMER: 182094010

#DB: 0000210C

 210 18:10:01 30

#ALPHA: 30

Note that Natural lets me WRITE ‘=’ #hours (em=yyyymmddhhiiss); and it makes a mess of it (again, because of what it considers a null date?). The difference between the two dates is clearly less than 7 months; yet Natural tells me 7 is the month (null month is 1) and 30 is the days. Totally useless information. Okay, I could subtract one from the 7 to get 6; but is even that correct?

In a “normal” date, YYYY MM DD are all meaningful. Clearly for a duration, this is not true. While “elapsed days” would be meaningful (although I might have to supply more than two D’s), MM is clearly not going to be meaningful. It would at best be based on the given dates, and not be applicable anywhere else. The same, of course, is true for YYYY (leap year or not?).

If I have a Duration, I should NOT be allowed to use Y’s or M’s in an edit mask. This should be a runtime error.

How about D’s? Perhaps I would be required to use DDD (or more D’s) so Natural knows I want what I would call a true days duration, not a day within month, which is what it does with “normal” dates.

Now how would Natural know the difference between a “true time” and a “duration time”? Perhaps when subtracting times, Natural could make the lower order character different. Say instead of a C (true time) an E (duration). Combine the different character with a requirement that DDD be used for durations, and I think this would work quite nicely.

Perhaps a change enhancement is in order. This would be for both output and MOVE EDITED.

steve

I agree on there not being a need to use a P13 redefinition for addition or subtraction but performing multiplication or division on T variables has caused me problems in the past. You can end up with results that are not very accurate. Using the P13 redefinition in the calculation yields more accurate results.

Quick example program


DEFINE DATA                                                        
LOCAL                                                              
1 #QTY    (P7.1) INIT <7.8>                                        
1 #TIME   (T)    INIT <T'00:05:30'>                                
1 REDEFINE #TIME                                                   
  2 #TIME-P (P13)                                                  
1 #RESULT (T)                                                      
1 REDEFINE #RESULT                                                 
  2 #RESULT-P (P13)                                                
END-DEFINE                                                         
COMPUTE ROUNDED #RESULT = #TIME / #QTY                             
WRITE 'T:' #TIME (EM=II:SS'.'T) '/' #QTY '=' #RESULT (EM=II:SS'.'T)
COMPUTE ROUNDED #RESULT-P = #TIME-P / #QTY                         
WRITE 'P:' #TIME (EM=II:SS'.'T) '/' #QTY '=' #RESULT (EM=II:SS'.'T)
COMPUTE ROUNDED #RESULT = #TIME * #QTY                             
WRITE 'T:' #TIME (EM=II:SS'.'T) '*' #QTY '=' #RESULT (EM=II:SS'.'T)
COMPUTE ROUNDED #RESULT-P = #TIME-P * #QTY                         
WRITE 'P:' #TIME (EM=II:SS'.'T) '*' #QTY '=' #RESULT (EM=II:SS'.'T)
END                                                                

Results

T: 05:30.0 /        7.8 #RESULT: 00:42.0
P: 05:30.0 /        7.8 #RESULT: 00:42.3
T: 05:30.0 *        7.8 #RESULT: 38:30.0
P: 05:30.0 *        7.8 #RESULT: 42:54.0

This has been an interesting discussion.

I am curious to know the use case for multiplying or dividing a time field by a number. I guess you are treating the time field as a duration, 5 minutes and 30 seconds, in which case your example makes some sense. But to Natural, T"00:05:30.0" is not 5 minutes and 30 seconds, but 12:05AM on Jan 2, 0000. Half of 5 minutes and 30 seconds is 2 minutes and 15 seconds, but I have no idea what half of 12:05AM Jan 2, 0000 is, and I wouldn’t expect Natural to know either.

So while your math with the P13 field works for small time values, it breaks down when the result is more than one day.

DEFINE DATA                                                        
LOCAL                                                              
1 #QTY        (P7.1) INIT <09.0>                                   
1 #TIME       (T)    INIT <T'12:30:00'> (EM=MM-DD-YYYY¬HH:II:SS.T) 
1 REDEFINE #TIME                                                   
  2 #TIME-P   (P13)                                                
1 #RESULT     (T)                       (EM=MM-DD-YYYY¬HH:II:SS.T) 
1 REDEFINE #RESULT                                                 
  2 #RESULT-P (P13)                                                
END-DEFINE                                                         
COMPUTE ROUNDED #RESULT = #TIME / #QTY                             
WRITE 'T:' #TIME '/' #QTY '=' #RESULT                              
COMPUTE ROUNDED #RESULT-P = #TIME-P / #QTY                         
WRITE 'P:' #TIME  '/' #QTY '=' #RESULT                             
COMPUTE ROUNDED #RESULT = #TIME * #QTY                             
WRITE 'T:' #TIME '*' #QTY '=' #RESULT                              
COMPUTE ROUNDED #RESULT-P = #TIME-P * #QTY                         
WRITE 'P:' #TIME  '*' #QTY '=' #RESULT                             
END                                                                

Results:


T: 01-02-0000 12:30:00.0 /        9.0 #RESULT: 01-02-0000 01:23:20.0  
P: 01-02-0000 12:30:00.0 /        9.0 #RESULT: 01-02-0000 01:23:20.0  
T: 01-02-0000 12:30:00.0 *        9.0 #RESULT: 01-06-0000 16:30:00.0  
P: 01-02-0000 12:30:00.0 *        9.0 #RESULT: 01-06-0000 16:30:00.0  

As we have seen, this does not work well with date fields, because the origin date is not zero.

Cheers to Ralph for his novel use of T and D fields to get the duration. We can eliminate the third assign if we redefine #D:

DEFINE DATA LOCAL                                 
1 #TFROM (T)       INIT <E'07/05/2014 07:00:00'>  
1 #TTHRU (T)       INIT <E'07/25/2014 01:10:01'>  
1 #HOURS (T)       (HD='HH:MM:SS')                
1 #D (D)                                          
1 REDEFINE #D                                     
  2 #DAYS (P7)     (HD='Days')                    
END-DEFINE                                        
ASSIGN #HOURS = #TTHRU - #TFROM   /* duration     
ASSIGN #D     = #HOURS            /* extract days 
DISPLAY #DAYS                                     
        #HOURS                                    
END                                               

I would reiterate, using a (D) or (T) field to represent a duration is mixing apples and cabbage. You should be careful, know what you are doing, and comment the code so whoever comes along behind you knows what you did.

Yes, it is using a T variable to hold a duration. This was utilized in a time tracking system I helped a company develop. An employee starts working on a job, process 1000 pieces, then finishes the job 2 hours later, you can calculate how much time was required per piece.

While the durations in this system are less than 1 day, I don’t see where there is a breakdown when the result is greater than 1 day. I can subtract 20130101 00:00:00 from 20140101 00:00:00, end up with a value that when represented by an edit mask looks like 00010101 00:00:00 but really contains the value 315360000. This is exactly 365 days, represented in tenths of a second. If I divide that by two, I end up with 00000702 12:00:00, or 157680000, which is exactly 182.5 days, represented in tenths of a second. Sure, I can’t use EM=YYYYMMDD to come up with anything that makes sense, but I can determine that the value of 157680000 is 182 days and 12 hours, should I need to. When talking about durations, the date edit masks don’t make sense, and I think that is what Steve was frustrated about but I don’t see any problems with using a T variable to store a duration.

I agree that you should be careful and know what you are doing. Someone did not understand what they were doing in this system and stored a current date value as part of a T variable when only the time portion was appropriate. You very quickly blow up a Natural program when you have 1000 pieces that had a duration record of more than 2014 years. :slight_smile:

What approach would you take to capturing a start and end time, then determining a per piece cost from it?

Sorry. No time to proofread. Off to a meeting.