kafka-streams session windows retention period - apache-kafka-streams

I am doing POC on session windows for one of the stream requirements i have. For this i am using Session windows as this fits requirement where i have to aggregate the events with unique transaction id into list. Each transaction can have multiple events generated by different applications and push them to kafka. Below is the code
StreamsConfig streamsConfig = new StreamsConfig(getProperties());
Serde<String> stringSerde = Serdes.String();
Serde<Transaction> transactionSerde = StreamsSerdes.TransactionSerde();
Aggregator<String,Transaction, List<Transaction>> agg = (key, value, list)
-> {
list.add(value);
return list;
};
Merger<String, List<Transaction>> merger = (key, v1, v2) ->
Stream.concat(v1.stream(), v2.stream())
.collect(Collectors.toList());
Materialized<String,List<Transaction>,SessionStore<Bytes, byte[]>>
materialized = Materialized.<String,List<Transaction>>as(Stores
.persistentSessionStore
("trans-store", 1000 * 30)).withKeySerde(stringSerde).withValueSerde(StreamsSerdes
.TransactionsListSerde());
Initializer<List<Transaction>> init = () -> new ArrayList<>();
StreamsBuilder builder = new StreamsBuilder();
KTable<Windowed<String>, List<Transaction>> customerTransactionCounts =
builder.stream(TRANSACTIONS_TOPIC, Consumed.with(stringSerde, transactionSerde).withOffsetResetPolicy(LATEST))
.groupBy((noKey, transaction) -> transaction.getCustomerId(),
Serialized.with(stringSerde, transactionSerde))
.windowedBy(SessionWindows.with(10000).until(1000 * 30))
.aggregate(init,agg,merger,materialized);
customerTransactionCounts.toStream().print(Printed.<Windowed<String>, List<Transaction>>toSysOut()
.withLabel("Customer Transactions List").withKeyValueMapper((key, list) ->
("Current Time " + new Date().toString() + " Customer Id - " + key.key() +
" START " +
new Date
(key.window().start()).toString() + " --- END " + new Date(key.window().end()).toString()+ " " + list)));
KafkaStreams kafkaStreams = new KafkaStreams(builder.build(), streamsConfig);
kafkaStreams.cleanUp();
How does the retention period work here??
1) First i ingested some data with transaction id X with events date range from
START Mon Apr 16 22:25:40 EDT 2018 --- END Mon Apr 16 22:25:49 EDT 2018
All of them were aggregated as they fall into same session.
2) Next i ingested single record transaction id X time START Mon Apr 16 22:26:45 EDT 2018.
I see 1 record after commit interval as expected
Here as per my understanding the stream time changed to 22:26:45. At this point the records ingested above should be expired from state store as endtime < stream time - retention period (30 secs)
3) Next i ingested record single record transaction id X that falls into the same time range as the first set of events. I see all the records from the first step and the new current record in the aggregated results after commit interval.
Shouldn't the first set of record be expired from state store as they are past the retention period???
In the third step i was assuming i would get only one aggregated record as the older ones should have been removed. When would the retention period kick in to remove records from state store??

Retention time is a "minimum" -- for more efficient expiration, data is stored in so-called segments (based on time intervals), and segments are expired when all data in a segment passed retention time.

Related

Spring boot request based on dynamic time or response time of first request

I have 5 endpoint(IP Address and port) with time frequency like 10 minute.
Scenarios - I have to sent a request each endpoint based on response time of first request + frequency time(10 min).
Example
endpoint1(10.20.30.11/80) send a request at 10 AM. Got the response
of request at 10:02 AM. Now next request will be sent at -> response
time (10:02AM) + frequency time(10min) = 10:12 AM.
endpoint2(10.10.20.11/90) send a request at 10 AM. Got the response
of request at 10:05 AM. Now next request will be sent at -> response
time (10:05AM) + frequency time(10min) = 10:15 AM.
endpoint3(10.40.60.11/70) send a request at 10 AM. Got the response of
request at 10:10 AM. Now next request will be sent at -> response
time (10:10AM) + frequency time(10min) = 10:20 AM.
I have to schedules this task based on response time + frequency time.
I know how to do based on fixed frequency time as describe below but as Its based on response time + frequency time (dynamic time), dont know how to achieve that like get dynamic time every time from DB. Any suggestion or help.
void setup()
{
final TaskScheduler scheduler = this.taskScheduler();
// Read records of (endpoint,freq) from fooTaskRepository
for (FooTaskEntity t: fooTaskRepository.findAll()) {
// extract endpoint from t, build a Runnable from it
Runnable r = ... ;
// extract frequency and transform to Duration
Duration d = Duration.ofMinutes(...);
// Schedule
scheduler.scheduleAtFixedRate(r, d); // r -> rest template call, d - time frequency
}
}

How reduce one day from current in Pipeline Jenkins?

I can't reduce one day from current
def now = new Date();
print(now); // print Fri Sep 06 13:10:03 EEST 2019
print(now - 1.days); // not working
print(now - 1); // not working
Please help me. Thanks in advance
the solution works. There might be 2 problems though:
- the snippet you wrote has to be included in a script if you plan to execute it in a stage
- the DateGroovyMethods is not allowed to be used by default. You need administrator rights and to check the build log to allow the execution of that stuff.
The error will look like this:
Scripts not permitted to use staticMethod org.codehaus.groovy.runtime.DateGroovyMethods minus java.util.Date int. Administrators can decide whether to approve or reject this signature.
This is my test example:
pipeline {
agent any
stages {
stage('MyDate test') {
steps {
script {
def date = new Date()
print date
print date - 1
}
}
}
}
}
EDIT:
If you are not an administrator, you can replace the script block with sh 'date -d "-1 days"'
You can also use minus(1) instead of - 1:
def now = new Date();
print(now);
print(now.minus(1))
The best thing to do is to skip the use of Date entirely. java.util.Date is literally the oldest java implementation of date and time. The newest comes with Java 8. You can do it like this:
groovy:000> java.time.LocalDateTime.now().minusDays(1)
===> 2019-09-08T12:07:30.835557
groovy:000>
You can convert from Date to LocalDateTime as well if needed.
(Java syntax used here, as I do not know Groovy.)
tl;dr
Subtract 24-hours.
Instant.now().minus( Duration.ofHours( 24 ) ) // UTC.
…or…
Subtract one calendar day.
ZonedDateTime.now( ZoneId.of( "America/New_York" ) ).minusDays( 1 ) ) // Time zone for Toledo, Ohio, US.
java.time
Never use java.util.Date. That terrible class was supplanted years ago by the modern java.time classes with the adoption of JSR 310. Specifically replaced by Instant.
I can't reduce one day from current
What do you mean by “one day”?
Generic 24-hour days
Do you mean to subtract 24-hours?
Duration d = Duration.ofHours( 24 ) ;
Instant instant = Instant.now() ;
Instant twentyFourHoursAgo = instant.minus( d ) ;
The Instant class represents a moment in UTC with a resolution of nanoseconds.
Run this code live at IdeOne.com.
instant.now().toString(): 2019-09-09T18:48:17.106438Z
twentyFourHoursAgo.toString(): 2019-09-08T18:48:17.106438Z
Calendar days
Do you mean to subtract one calendar day?
This requires a time zone. For any given moment, the date varies around the globe by time zone. It may be “tomorrow” in Tokyo Japan while still “yesterday” in Toledo Ohio US.
Specify a time zone with ZoneId to capture the current moment as seen through the wall-clock time used by the people of a particular region in a ZonedDateTime object.
ZoneId z = ZoneId.of( "Asia/Tokyo" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;
ZonedDateTime oneDayAgo = zdt.minusDays( 1 ) ;
Run this code live at IdeOne.com.
zdt.toString(): 2019-09-10T03:48:17.147539+09:00[Asia/Tokyo]
oneDayAgo.toString(): 2019-09-09T03:48:17.147539+09:00[Asia/Tokyo]
Convert
If you must have a java.util.Date object to interoperate with old code not yet updated to java.time, you can convert. See the new to…/from… conversion methods added to the old classes.
java.util.Date javaUtilDate =
Date.from( Instant.now().minus( Duration.ofHours( 24 ) ) ) ;
…or…
java.util.Date javaUtilDate =
Date.from( ZonedDateTime.now( ZoneId.of( "Asia/Tokyo" ) ).minusDays( 1 ) ) ) ;
Keep in mind that java.util.Date.toString method tells a lie, dynamically applying the JVM’s current default time zone while generating the text. One of many reasons to avoid this badly-designed class.

Algorithm to calculate a date for complex occupation management

Hello fellow Stack Overflowers,
I have a situation, where I need some help choosing the best way to make an algorithm work, the objective is to manage the occupation of a resource (Lets consider the resource A) to have multiple tasks, and where each task takes a specified amount of time to complete. At this first stage I don't want to involve multiple variables, so lets keep it the simple way, lets consider he only has a schedule of the working days.
For example:
1 - We have 1 resource, resource A
2 - Resource A works from 8 am to 4 pm, monday to friday, to keep it simple by now, he doesn't have lunch for now, so, 8 hours of work a day.
3 - Resource A has 5 tasks to complete, to avoid complexity at this level, lets supose each one will take exactly 10 hours to complete.
4 - Resource A will start working on this tasks at 2018-05-16 exactly at 2 pm.
Problem:
Now, all I need to know is the correct finish date for all the 5 tasks, but considering all the previous limitations.
In this case, he has 6 working days and additionaly 2 hours of the 7th day.
The expected result that I want would be: 2018-05-24 (at 4 pm).
Implementation:
I thought about 2 options, and would like to have feedback on this options, or other options that I might not be considering.
Algorithm 1
1 - Create a list of "slots", where each "slot" would represent 1 hour, for x days.
2 - Cross this list of slots with the hour schedule of the resource, to remove all the slots where the resource isn't here. This would return a list with the slots that he can actually work.
3 - Occupy the remaining slots with the tasks that I have for him.
4 - Finnaly, check the date/hour of the last occupied slot.
Disadvantage: I think this might be an overkill solution, considering that I don't want to consider his occupation for the future, all I want is to know when will the tasks be completed.
Algorithm 2
1 - Add the task hours (50 hours) to the starting date, getting the expectedFinishDate. (Would get expectedFinishDate = 2018-05-18 (at 4 pm))
2 - Cross the hours, between starting date and expectedFinishDate with the schedule, to get the quantity of hours that he won't work. (would basically get the unavailable hours, 16 hours a day, would result in remainingHoursForCalc = 32 hours).
3 - calculate new expectedFinishDate with the unavailable hours, would add this 32 hours to the previous 2018-05-18 (at 4 pm).
4 - Repeat point 2 and 3 with new expectedFinishDate untill remainingHoursForCalc = 0.
Disadvantage: This would result in a recursive method or in a very weird while loop, again, I think this might be overkill for calculation of a simple date.
What would you suggest? Is there any other option that I might not be considering that would make this simpler? Or you think there is a way to improve any of this 2 algorithms to make it work?
Improved version:
import java.util.Calendar;
import java.util.Date;
public class Main {
public static void main(String args[]) throws Exception
{
Date d=new Date();
System.out.println(d);
d.setMinutes(0);
d.setSeconds(0);
d.setHours(13);
Calendar c=Calendar.getInstance();
c.setTime(d);
c.set(Calendar.YEAR, 2018);
c.set(Calendar.MONTH, Calendar.MAY);
c.set(Calendar.DAY_OF_MONTH, 17);
//c.add(Calendar.HOUR, -24-5);
d=c.getTime();
//int workHours=11;
int hoursArray[] = {1,2,3,4,5, 10,11,12, 19,20, 40};
for(int workHours : hoursArray)
{
try
{
Date end=getEndOfTask(d, workHours);
System.out.println("a task starting at "+d+" and lasting "+workHours
+ " hours will end at " +end);
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}
}
public static Date getEndOfTask(Date startOfTask, int workingHours) throws Exception
{
int totalHours=0;//including non-working hours
//startOfTask +totalHours =endOfTask
int startHour=startOfTask.getHours();
if(startHour<8 || startHour>16)
throw new Exception("a task cannot start outside the working hours interval");
System.out.println("startHour="+startHour);
int startDayOfWeek=startOfTask.getDay();//start date's day of week; Wednesday=3
System.out.println("startDayOfWeek="+startDayOfWeek);
if(startDayOfWeek==6 || startDayOfWeek==0)
throw new Exception("a task cannot start on Saturdays on Sundays");
int remainingHoursUntilDayEnd=16-startHour;
System.out.println("remainingHoursUntilDayEnd="+remainingHoursUntilDayEnd);
/*some discussion here: if task starts at 12:30, we have 3h30min
* until the end of the program; however, getHours() will return 12, which
* substracted from 16 will give 4h. It will work fine if task starts at 12:00,
* or, generally, at the begining of the hour; let's assume a task will start at HH:00*/
int remainingDaysUntilWeekEnd=5-startDayOfWeek;
System.out.println("remainingDaysUntilWeekEnd="+remainingDaysUntilWeekEnd);
int completeWorkDays = (workingHours-remainingHoursUntilDayEnd)/8;
System.out.println("completeWorkDays="+completeWorkDays);
//excluding both the start day, and the end day, if they are not fully occupied by the task
int workingHoursLastDay=(workingHours-remainingHoursUntilDayEnd)%8;
System.out.println("workingHoursLastDay="+workingHoursLastDay);
/* workingHours=remainingHoursUntilDayEnd+(8*completeWorkDays)+workingHoursLastDay */
int numberOfWeekends=(int)Math.ceil( (completeWorkDays-remainingDaysUntilWeekEnd)/5.0 );
if((completeWorkDays-remainingDaysUntilWeekEnd)%5==0)
{
if(workingHoursLastDay>0)
{
numberOfWeekends++;
}
}
System.out.println("numberOfWeekends="+numberOfWeekends);
totalHours+=(int)Math.min(remainingHoursUntilDayEnd, workingHours);//covers the case
//when task lasts 1 or 2 hours, and we have maybe 4h until end of day; that's why i use Math.min
if(completeWorkDays>0 || workingHoursLastDay>0)
{
totalHours+=8;//the hours of the current day between 16:00 and 24:00
//it might be the case that completeWorkDays is 0, yet the task spans up to tommorrow
//so we still have to add these 8h
}
if(completeWorkDays>0)//redundant if, because 24*0=0
{
totalHours+=24*completeWorkDays;//for every 8 working h, we have a total of 24 h that have
//to be added to the date
}
if(workingHoursLastDay>0)
{
totalHours+=8;//the hours between 00.00 AM and 8 AM
totalHours+=workingHoursLastDay;
}
if(numberOfWeekends>0)
{
totalHours+=48*numberOfWeekends;//every weekend between start and end dates means two days
}
System.out.println("totalHours="+totalHours);
Calendar calendar=Calendar.getInstance();
calendar.setTime(startOfTask);
calendar.add(Calendar.HOUR, totalHours);
return calendar.getTime();
}
}
You may adjust the hoursArray[], or d.setHours along with c.set(Calendar.DAY_OF_MONTH, to test various start dates along with various task lengths.
There is still a bug , due to the addition of the 8 hours between 16:00 and 24:00:
a task starting at Thu May 17 13:00:00 EEST 2018 and lasting 11 hours will end at Sat May 19 00:00:00 EEST 2018.
I've kept a lot of print statements, they are useful for debugging purposes.
Here is the terminology explained:
I agree that algorithm 1 is overkill.
I think I would make sure I had the conditions right: hours per day (8), working days (Mon, Tue, Wed, Thu, Fri). Would then divide the hours required (5 * 10 = 50) by the hours per day so I know a minimum of how many working days are needed (50 / 8 = 6). Slightly more advanced, divide by hours per week first (50 / 40 = 1 week). Count working days from the start date to get a first shot at the end date. There was probably a remainder from the division, so use this to determine whether the tasks can end on this day or run into the next working day.

ZonedDateTime would use same timezone of winter when summer daylight saving?

ZonedDateTime zdt = ZonedDateTime.of(2015, 10, 18, 0, 30, 0, 0,
ZoneId.of("America/Sao_Paulo"));
System.out.println(zdt); // 2015-10-18T01:30-02:00[America/Sao_Paulo]
You can see the hour is 1 while we set the hour as 0, and timezone is UTC-02:00 while daylight saving timezone should be UTC-03:00.
But here is a different example:
ZonedDateTime zdt = ZonedDateTime.of(2015, 10, 18, 0, 30, 0, 0,
ZoneId.of("America/Los_Angeles"));
System.out.println(zdt); //2015-10-18T00:30-07:00[America/Los_Angeles]
You can see the daylight saving timezone is UTC-07:00 and the hour is 0 as we set.
Why are they different?
This happens because the time you picked falls in the gap between midnight and 01:00 on the night when Brazil switches to summer time. That time is actually impossible and so you get the behavior described in the documentation:
In the case of a gap, when clocks jump forward, there is no valid offset. Instead, the local date-time is adjusted to be later by the length of the gap. For a typical one hour daylight savings change, the local date-time will be moved one hour later into the offset typically corresponding to "summer".
You can observe the same behavior in Los_Angeles zone by picking a time between 02:00 and 03:00 on the corresponding night in March:
zdt = ZonedDateTime.of(2015, 3, 8, 2, 30, 0, 0,
ZoneId.of("America/Los_Angeles"));
System.out.println(zdt);
As already explained in #Misha's answer, it happens due to Daylight Saving Time rules.
In São Paulo, DST starts at the midnight of 2015-10-18: the clocks move forward 1 hour, so it "jumps" from 23:59:59 to 01:00:00. There's a gap between 00:00:00 and 00:59:59, so the time 00:30 is adjusted accordingly.
You can check if the date and time are valid for the timezone using the ZoneRules and ZoneOffsetTransition classes:
ZoneId sp = ZoneId.of("America/Sao_Paulo");
ZoneRules rules = sp.getRules();
// check if 2015-10-18 00:30 is valid for this timezone
LocalDateTime dt = LocalDateTime.of(2015, 10, 18, 0, 30);
List<ZoneOffset> validOffsets = rules.getValidOffsets(dt);
System.out.println(validOffsets.size()); // size is zero, no valid offsets at 00:30
The getValidOffsets method returns all the valid offsets for the specified date/time. If the list is empty, it means the date/time doesn't "exist" in that timezone (usually because of DST the clocks jump forward).
When the date/time exists in a timezone, an offset is returned:
ZoneId la = ZoneId.of("America/Los_Angeles");
rules = la.getRules();
validOffsets = rules.getValidOffsets(dt);
System.out.println(validOffsets.size()); // 1 - date/time valid for this timezone
System.out.println(validOffsets.get(0)); // -07:00
For Los_Angeles timezone, 1 valid offset is returned: -07:00.
PS: Offset changes usually occur due to DST, but that's not always the case. DST and offsets are defined by governments and laws, and they can change at anytime. So, a gap in the valid offset can also mean that such change occured (some politician decided to change the standard offset of the country, so the gap might not necessarily be related to DST).
You can also check when the change occurs, and what's the offset before and after it:
ZoneId sp = ZoneId.of("America/Sao_Paulo");
ZoneRules rules = sp.getRules();
// get the previous transition (the last one that occurred before 2015-10-18 00:30 in Sao_Paulo timezone
ZoneOffsetTransition t = rules.previousTransition(dt.atZone(sp).toInstant());
System.out.println(t);
The output is:
Transition[Gap at 2015-10-18T00:00-03:00 to -02:00]
It means that there's a gap (clocks moving forward) at 2015-10-18T00:00, and the offset will change from -03:00 to -02:00 (so, clock moves 1 hour forward).
You can also get all these info separately:
System.out.println(t.getDateTimeBefore() + " -> " + t.getDateTimeAfter());
System.out.println(t.getOffsetBefore() + " -> " + t.getOffsetAfter());
The output is:
2015-10-18T00:00 -> 2015-10-18T01:00
-03:00 -> -02:00
It shows that, at 00:00 the clock moves directly to 01:00 (so 00:30 can't exist). In the second line, the offsets before and after the change.
If you check the transitions in Los_Angeles timezone, you'll see that its DST starts and ends at different dates:
ZoneId la = ZoneId.of("America/Los_Angeles");
rules = la.getRules();
// 2015-10-18 00:30 in Los Angeles
Instant instant = dt.atZone(la).toInstant();
System.out.println(rules.previousTransition(instant));
System.out.println(rules.nextTransition(instant));
The output is:
Transition[Gap at 2015-03-08T02:00-08:00 to -07:00]
Transition[Overlap at 2015-11-01T02:00-07:00 to -08:00]
So, in Los_Angeles timezone, DST starts at 2015-03-08 and ends at 2015-11-01. That's why at 2015-10-18, all hours are valid (there's no adjustment as it happens in Sao_Paulo timezone).
Some timezones have transition rules (like "DST starts at the third Sunday of October") instead of just transitions (like "DST starts at this specific date and time"), and you can also use them, if available:
ZoneId sp = ZoneId.of("America/Sao_Paulo");
ZoneRules rules = sp.getRules();
// hardcoded: Sao_Paulo timezone has 2 transition rules, the second one is relative to October
// but you should always check if the list is not empty
ZoneOffsetTransitionRule tr = rules.getTransitionRules().get(1);
// get the transition for year 2015
ZoneOffsetTransition t = tr.createTransition(2015);
// use t the same way as above (the output will be the same)
Another way to check if a date and time is valid for some timezone is to use the ZonedDateTime.ofStrict method, that throws an exception if the date and time is invalid for a timezone:
ZoneId sp = ZoneId.of("America/Sao_Paulo");
ZoneId la = ZoneId.of("America/Los_Angeles");
LocalDateTime dt = LocalDateTime.of(2015, 10, 18, 0, 30);
System.out.println(ZonedDateTime.ofStrict(dt, ZoneOffset.ofHours(-7), la)); // OK
System.out.println(ZonedDateTime.ofStrict(dt, ZoneOffset.ofHours(-3), sp)); // throws java.time.DateTimeException
The first case is OK, because an offset of -7 is valid for Los Angeles, for the given date/time. The second case throws an exception because an offset of -3 is invalid for São Paulo, at the given date/time.

Converting dates in msg file to match outlook local dates muddle

I am taking the MSG file and changing the date to match the local date in outlook. (same Sent date when you open the message in outlook).
But the times seem to be off 1 hour or many hours. I've dumped out the times to investigate further:-
I'm using Aspose to open the msg file.
c#
1.msg Shows: 05/01/2011 00:46 in outlook 2016. GMT
05/01/2011 00:46:07, Kind = Utc
TimeZone offset = -08:00:00
Actual UTC Time is 05/01/2011 00:46:07
Message date to universal time + timezoneoffset: 04/01/2011 16:46:07
timezoneoffset: -08:00:00
calculated universal msg date: 04/01/2011 16:46:07
output 04/01/2011 08:46 AM
2.msg Shows 20/06/2016 16:25 in outlook 2016. GMT
20/06/2016 16:25:23, Kind = Local
TimeZone offset = 02:00:00
Actual UTC Time is 20/06/2016 15:25:23
Message date to universal time + timezoneoffset: 20/06/2016 17:25:23
timezoneoffset: 02:00:00
calculated universal msg date: 20/06/2016 17:25:23
output 06/20/2016 07:25 PM
any ideas on how to correct this to show same as outlook? I see some times are UTC and some are Local, is there a solution anyone know of?
MailMessage msg = MailMessage.Load(inFile);
MemoryStream ms = new MemoryStream();
Console.WriteLine(msg.Date.ToString() + ", Kind = " + msg.Date.Kind);
Console.WriteLine("TimeZone offset = " + msg.TimeZoneOffset);
Console.WriteLine("Actual UTC Time is " + msg.Date.ToUniversalTime().ToString());
Console.WriteLine("Message date to universal time + timezoneoffset: " + (msg.Date.ToUniversalTime() +
msg.TimeZoneOffset).ToString());
Console.WriteLine("timezoneoffset: " + msg.TimeZoneOffset.ToString());
// do calculation.
TimeZone localZone = TimeZone.CurrentTimeZone;
TimeSpan ts = localZone.GetUtcOffset(msg.Date);
msg.Date = msg.Date + msg.TimeZoneOffset;
Console.WriteLine("calculated universal msg date: " +msg.Date.ToUniversalTime().ToString());
if (msg.Date.Second >= 30) // for rounding up to match outlook
{
// ... Days, hours, minutes, seconds, milliseconds.
TimeSpan span = new TimeSpan(0, 0, 0, 31, 0);
msg.Date = msg.Date + span;
}
MhtMessageFormatter mhtlFormat = new MhtMessageFormatter();
mhtlFormat.DateTimeFormat = "ddd MM/dd/yyyy hh:mm tt";
mhtlFormat.Format(msg);
MhtSaveOptions mhtSaveOptions = new MhtSaveOptions();
mhtSaveOptions.MhtFormatOptions = MhtFormatOptions.None;
mhtSaveOptions.MhtFormatOptions = mhtSaveOptions.MhtFormatOptions | MhtFormatOptions.HideExtraPrintHeader;
Thanks,
Lee.
As assisted on your thread in Aspose.Email forum, we believe your query has already met an answer. If you still find any issue or have confusion, you can inquire further on respective thread.
I work with Aspose as Developer Evangelist.

Resources